in-place 原地算法
概念
in-place algorithm 算法是一种使用小的,固定数量的额外之空间来转换资料的算法。当算法执行时,输入的资料通常会被要输出的部份覆盖掉。
主要特征
假设文本串text长度为n,模式串pattern长度为m,BM算法的主要特征为:
- 从右往左进行比较匹配(一般的字符串搜索算法如KMP都是从从左往右进行匹配);
- 算法分为两个阶段:预处理阶段和搜索阶段;
- 预处理阶段时间和空间复杂度都是是O(m+),是字符集大小,一般为256;
- 搜索阶段时间复杂度是O(mn);
- 当模式串是非周期性的,在最坏的情况下算法需要进行3n次字符比较操作;
- 算法在最好的情况下达到O(n/m),比如在文本串b中搜索模式串ab ,只需要n/m次比较。
BM算法的精华就在于BM(text, pattern),也就是BM算法当不匹配的时候一次性可以跳过不止一个字符。即它不需要对被搜索的字符串中的字符进行逐一比较,而会跳过其中某些部分。通常搜索关键字越长,算法速度越快。它的效率来自于这样的事实:对于每一次失败的匹配尝试,算法都能够使用这些信息来排除尽可能多的无法匹配的位置。即它充分利用待搜索字符串的一些特征,加快了搜索的步骤。
BM算法实际上包含两个并行的算法(也就是两个启发策略):坏字符算法(bad-character shift)和好后缀算法(good-suffix shift)。这两种算法的目的就是让模式串每次向右移动尽可能大的距离(即上面的BM()尽可能大)。
应用案例
求众数
给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在众数
示例 1:
输入: [3,2,3]
输出: 3
示例 2:
输入: [2,2,1,1,1,2,2]
输出: 2
该题的解法不止一种,其中在leetcode官方题解中Boyer-Moore 投票算法比较经典,这这里复现一下。
想法
如果我们把众数记为 +1 ,把其他数记为 -1 ,将它们全部加起来,显然和大于 0 ,从结果本身我们可以看出众数比其他数多。
算法
本质上, Boyer-Moore 算法就是找 nums 的一个后缀 suf
,其中 suf[0]
就是后缀中的众数。我们维护一个计数器,如果遇到一个我们目前的候选众数,就将计数器加一,否则减一。只要计数器等于 0 ,我们就将 nums 中之前访问的数字全部 忘记 ,并把下一个数字当做候选的众数。直观上这个算法不是特别明显为何是对的,我们先看下面这个例子(竖线用来划分每次计数器归零的情况)
[7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7]
首先,下标为 0 的 7 被当做众数的第一个候选。在下标为 5 处,计数器会变回0 。所以下标为 6 的 5 是下一个众数的候选者。由于这个例子中 7 是真正的众数,所以通过忽略掉前面的数字,我们忽略掉了同样多数目的众数和非众数。因此, 7 仍然是剩下数字中的众数。
[7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 5, 5, 5, 5]
现在,众数是 5 (在计数器归零的时候我们把候选从 7 变成了 5)。此时,我们的候选者并不是真正的众数,但是我们在 遗忘 前面的数字的时候,要去掉相同数目的众数和非众数(如果遗忘更多的非众数,会导致计数器变成负数)。
因此,上面的过程说明了我们可以放心地遗忘前面的数字,并继续求解剩下数字中的众数。最后,总有一个后缀满足计数器是大于 0 的,此时这个后缀的众数就是整个数组的众数。
/**
* @param {number[]} nums
* @return {number}
*/
var majorityElement = function(nums) {
let count=1;
let len=nums.length/2;
let obj={};
let res=1;
nums.forEach(item=>{
if(obj[item]){
count=++obj[item]
}else{
obj[item]=1
}
if(count>len&&obj[item]==count)res=item;
})
return res
};
复杂度分析
时间复杂度:
O(n)
Boyer-Moore 算法严格执行了 n 次循环,所以时间复杂度是线性时间的。空间复杂度:
O(1)
Boyer-Moore 只需要常数级别的额外空间。