Boyer-Moore:A Linear Time Majority Vote Alogrithm,这是最基础的最大投票算法
。html
原文中提到:decides which element of a sequence is in the majority, provided there is such an element.
,可是讲的有一些含糊。我再补充一下:在一次投票中,若是某一种投票出现的数量大于(这里必须是大于而不能是等于,不然在某些特殊条件下会获得错误结果)总投票,咱们就认为这种投票是咱们要找的 Majority Element。java
参考 Leetcode 上的这道题:169.Majority Element算法
Given an array of size n, find the Majority Element. The Majority Element is the element that appears more than ⌊ n/2 ⌋ times.数组
You may assume that the array is non-empty and the Majority Element always exist in the array.app
算法的具体思路是:假设在给定长度为 n 的数组中,Majority Element 出现的次数是 k
次,那么非 Majority Element 的出现次数就为 n-k
。若是咱们能去掉这 n-k
个元素那么剩下的就所有是 Majority Element 了。ide
咱们能够遍历数组,当碰到两个不同的数字时,咱们将这两个数字同时丢弃这两个数字中可能有一个为 Majority Element,也可能两个都不为Majority Element.由于k
大于 n/2
,因此在最差状况下(每次移除不一样数字时都包含一个Majority Element),咱们仍然可以保证最后获得的数字是Majority Element.idea
在网上看到的不少资料中,对这一步的解释都是略微有些问题的。不少人简单的将这一步解释为:找到一个Majority Element
,随后找到一个 非Majority Element
,并将他们一并移除,这实际上是错误的。咱们在循环的时候,并无办法断定当前的数字是否为 Majority Element
,因此在移除的时候,咱们多是移除了一个 Majority Element
和一个 非Majority Element
,也有可能移除的是两个非Majority Element
。因此最后 count
的值是不肯定的,可是它可以保证在最差状况下,剩余的仍然是 Majority Element。例如,[1,2,3,3,3] 和 [1,3,2,3,3] 这两个数组最后获得的 count
分别为 3 和 1,可是这并不影响答案的正确性。spa
这也是前面提到的Majority Element的数量必须大于n/2
的缘由.code
很容易算出最后剩余的Majority Element个数最少为: n - ((n - k) + (n - k)) = 2k - n。htm
public class Solution { public int majorityElement(int[] nums) { int candidate = 0; for(int i = 0,count = 0; i < nums.length; i++){ //问题一: if 的断定顺序有要求吗?若是有要求的话应该是怎么样的呢? if(count == 0){ count++; candidate = nums[i]; }else if(candidate != nums[i]){ count--; }else{ count++; } } return candidate; } }
这个算法很经典,也很简单,毕竟不用本身想。
接下来,咱们能够对这个算法作一些简单的扩展,咱们当前定义的 Majority Element 的数量大于 n/2 的元素。
若是咱们在投票只要知足投票数量超过 n/3 即认为它是最大投票,咱们能不能求出这个值呢?
妈蛋,文章中这种问题就跟小说里主角跳崖会不会死同样,有标准答案的。乔治啊啊马丁:?
最大投票资料片:熊猫人之谜 229. Majority Element II
Given an integer array of size n, find all elements that appear more than
⌊ n/3 ⌋
times. The algorithm should run in linear time and in O(1) space.
思路依然同 Majority Element 同样,不一样的是咱们须要两个 Majority Element 的候选者,同时须要两个 count 分别对候选者进行计数。
count 为 candidate 当前出现的次数。count == 0 说明当前 candidate 对应的候选者已经被移除,咱们须要设定一个新的候选者。
public class Solution { public List<Integer> majorityElement(int[] nums) { //问题二:这里给 candidate0 candidate1 初始化值为 0,这会不会影响咱们运行的结果? int candidate0 = 0,candidate1 = 0,count0 = 0, count1 = 0; for(int i = 0; i < nums.length; i++){ if(candidate0 == nums[i]){ //当前数字等于一号候选数字 count0++; }else if(candidate1 == nums[i]){ //当前数字等于二号候选数字 count1++; }else if(count0 == 0){ //当前数字不等于一号候选数字或二号候选数字 //同时必须知足 count 等于 0,由于若是 count != 0,说明还有候选数字在等待与它一组的另外两个数字 count0++; candidate0 = nums[i]; }else if(count1 == 0){ count1++; candidate1 = nums[i]; }else{ //只有 不知足以上全部条件咱们才能对 count 进行减操做 count0--; count1--; } } //**问题三:这里可以省略 distinct() 吗?为何?** return Stream.of(candidate0, candidate1).distinct().filter(num -> { int count = 0; for(int i = 0; i < nums.length; i++){ if(nums[i] == num){ count++; } } return count > nums.length / 3; }).collect(Collectors.toList()); } }
咱们再梳理一遍思路:咱们须要找到三个不一样的数字,而后抛弃掉这三个数字:
首先要判断是否等于candidate
,若是等于candidate
那么对应的candidate
必须加一等待其余的数字来消除它
当有一个candidate
的count
为 0 时,说明该candidate
已经所有被消除,咱们须要设定新的candidate
数字。
当一个数字不等于两个candidate
,同时两个candidate
的count
都不为零。这意味着当前这个数字就是这两个candidate
等待的第三个数字。因而这三个数字被移除,同时他们的count
都要减一。
这个算法到这里就结束了,时间复杂度是线性的 O(n),空间复杂度是 O(1)。
接下来是问题解答时间:
问题一: if 的断定顺序有要求吗?若是有要求的话应该是怎么样的呢?
答案是有要求,细心的读者可能发现,在 Majority Element
中,咱们对 count == 0
的判断在对 candidate == nums[i]
的判断以前,而在 Majority Element II
中则正好相反。
这是由于,count == 0
是用来判断对应 candidate
的当前存活量,在判断这一步以前,咱们必须确保数组中当前数字不等于 两个 candidate
中的任意一个。不然,咱们可能会在 count0!=0 && count1==0 && nums[i]==candidate0
时错误的将 nums[i] 赋值给 candidate1。
问题二:这里给 candidate0 candidate1 初始化值为 0,这会不会影响咱们运行的结果?
不会,由于 candidate0
只会在第一次循环中使用,若是 candidate0 == nums[0]
,count++
不会引发任何问题。若是 candidate != nums[0]
那么咱们此时 count==0
从新初始化 candidate0 == nums[0]
,一样不会有任何影响。
问题二扩充:若是咱们初始化 int candidate0 = 0, candidate1 = 1
会不会影响咱们的运行结果呢?
问题三:这里可以省略 distinct() 吗?为何?
不能,尽管咱们在循环中首先经过 if(candidate0 == nums[i])
和 else if(candidate1 == nums[i])
两个 if 判断,使得 candidate0 != candidate1
在绝大部分下成立,可是在一种极为特殊的状况下仍然可能会使得咱们获得重复的数组。
试想当整个数组全部的数字都相等的时候,咱们 candidate0
和 candidate1
这两个候选数字中,有一个数字将永远不会被从新赋值,也就是说,有一个数字将咱们赋给的初值保持到了最后。
在咱们的代码中,由于咱们将两个候选数字都初始化 0,因此当数组 全为0 时会返回错误的结果。
这一点,咱们能够经过将两个候选数字初始化为不一样的数字来解决:int candidate0 = 0,candidate1 = 1
,这样咱们就能够移除掉 distinct() 了