刷算法题的时候刷到这样一个题:
给定一个大小为
n
的数组nums
,返回其中的多数元素。多数元素是指在数组中出现次数 大于⌊ n/2 ⌋
的元素。你可以假设数组是非空的,并且给定的数组总是存在多数元素。
这是一道简单题,但是要求设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题 ,然后我就呆住了。没有这个限制的话其实还是很容易的,比如
- 创建一个map来统计各个元素的数量从而找到多数元素
- 还可以对该数组进行排序,然后直接输出下标位于数组中间的位置
- 也可以使用分治的思想递归求解,将数组分为左右两个区间,然后分别选出左右两个区间的众数,直到每一组的元素都是一个后直接返回该元素。如果回溯后某区间的长度大于1,须将左右两个区间合并,并比较左右两个区间的众数,若是众数相同则直接返回,若是不同,则比较两个数在区间内出现的次数,返回出现次数多的那个数。
但是很显然上述的方法都不能满足题目对时间和空间复杂度的要求。第一个方法的时间和空间的复杂度都是O(n),第二个的时间和空间复杂度由选择的排序算法决定,第三个的时间复杂度为O(nlogn),空间复杂度为O(logn)。
看完题解之后才恍然大悟,而且解法十分巧妙,所以想写篇文章记录和分享一下。
解法的名字叫做Boyer-Moore 投票算法
从题目给的条件本身来看,一定有一个数出现的次数比其他所有数要高。
举一个形象的例子,有一块地,由敌方所有军队的联盟与我方一支军队抢夺且我方军队人数大于敌方所有军队联盟人数,最终谁站在土地上,其所属阵营便能抢占该土地,每次只能上一个人,若属统一联盟,则一起抢占,若出现敌我两方,则一命换一命,显然因为我方人数最多,最后抢夺到土地的一定是我方。
类比过来,我方就是数组出现次数最多的数,敌方就是其他数加起来出现的次数。抢占土地的规则就是选取一个候选人, 若是当前的元素与候选人不一样,则count-1,若是一样就+1;而count不为0时候选人不变,若是count为0,则选取当前的元素为候选人。
候选人就类似于站在土地上的人,count为0则土地上没人,下一个上来的人成为候选人,count不为0时候选人不变,若属统一联盟,则一起抢占,count+1,若出现敌我两方,则一命换一命,count-1。因为我方军队人数最多,所以最后站在土地上的人一定是我方,即最终候选人是我方的人,count也肯定不为0。
最后根据以上逻辑编码代码:
class Solution {
public int majorityElement(int[] nums) {
int count = 0;
int candidate = 0;
for(int i = 0; i<nums.length;i++){
if(count == 0){
candidate = nums[i];
}
if(candidate != nums[i]){
count--;
}else{
count++;
}
}
return candidate;
}
}
以上代码的时间复杂度为O(n),空间复杂度为O(1)。
这题虽然被Leecode归为简单题,但是我属实是不看题解想不到这方法。。