摩尔投票法
问题描述: 假设我们的数组有 nn 个元素, 我们要找到其中出现次数超过一半的元素
算法流程:
-
从这 nn 个元素中选一个作为
candidate
,记录它的票数为votes = 1
. -
此时我们的数组中还有 n−1n−1 个元素, 我们每次都取出一个元素(记为
current
), 并重复执行以下的步骤(一共n-1
次)- 将它和我们当前的
candidate
做比较, 如果它们的值一样, 那么votes++
, 也就是投赞同票 - 如果它们的值不一样,
votes--
, 也就是投反对票. 如果此时votes = 0
的话, 那么candidate <- current
, 也就是说我们让current
成为了新的candidate
, 并记votes = 1
- 将它和我们当前的
-
最后
candidate
的值就可能是我们想要的出现次数超过一半的元素, 此时我们得再遍历一遍数组进行计数看它到底是不是
在看完上面的算法流程之后, 你可能跟我一样感到很困惑. 为什么这样最后我们就能找到出现次数超过一半的元素. 其实只要想明白一个原理就很简单
💡 出现次数超过 ⌊n/2⌋⌊n/2⌋ 次的元素如果存在, 此时数组中的其他元素一定是出现次数小于 ⌊n/2⌋⌊n/2⌋ 的
这句话有什么用呢 ?
因为摩尔投票法的做法其实就是投票
- 可以是投赞同票, 此时相当于我们在统计这个元素出现的次数
- 可以是投反对票. 相当于我们撤销了一个同意票, 就是抵消抵消抵消!!!
但是因为出现次数超过一半的元素加起来的票数(赞同票) > 剩下所有不是的(反对票)这件事是一定成立的, 所以无论怎样最后赢的永远是出现次数超过一半的元素. 于是我们就找到了 😃
如果还是不懂可以看看下面的这个 GIF 图理解一下~
代码
class Solution {
public int majorityElement(int[] nums) {
if (nums.length < 2) {
return nums[0];
}
int candidates = nums[0];
int votes = 1;
// step 1. start to vote
for (int i = 0; i < nums.length; i++) {
if (nums[i] != candidates) {
votes -= 1;
if (votes == 0) {
candidates = nums[i];
votes = 1;
}
} else {
votes += 1;
}
}
// step2. check
int occurs = 0;
for (var val: nums) {
if (candidates == val) {
occurs += 1;
}
}
if (occurs >= nums.length / 2) {
return candidates;
} else {
return -1;
}
}
}