多数元素问题是算法面试中最经典的题之一:
给定数组
nums,其中一个元素的出现次数 超过 n/2 ,找出它。要求:
- 时间复杂度 O(n)
- 空间复杂度 O(1)
示例:
输入: [2,2,1,1,1,2,2]
输出: 2
题目保证多数元素一定存在。
一、核心思想:成对抵消
把数组看成选票,每个数字代表一个候选人。当两个不同的元素相遇时,可以视为一张支持票和一张反对票抵消了。
这就是把直接找最多的元素这个问题,转化成了数组间元素互相竞争计票的问题,思想上能转过这个弯来想到这个点,这便是一道Easy题。
由于多数元素(majority element)的票数大于其它所有元素票数的总和,它无法被完全抵消,因此最终剩下的候选人一定是多数元素。
这个思想催生了著名的 ------ Boyer--Moore Majority Vote Algorithm
二、实现步骤
首先我们要创建两个变量:
candidate:当前候选人count:候选人当前的"净票数"
遍历数组的每个元素 num:
-
如果
count == 0:说明之前的票已经两两抵消完,可以重选候选人:
candidate = num count = 1 -
如果
num == candidate:count++ -
否则:
count--
最终的 candidate 就是多数元素。
三、正确 Java 实现(O(n) 时间 + O(1) 空间)
java
class Solution {
public int majorityElement(int[] nums) {
int candidate = 0;
int count = 0;
for (int num : nums) {
if (count == 0) {
candidate = num;
count = 1;
} else if (num == candidate) {
count++;
} else {
count--;
}
}
return candidate;
}
}
这段代码能通过所有测试用例,是多数投票算法的标准写法。
四、特别提醒(⚠ 初学者最常犯的致命错误)
在实现多数投票算法时,许多同学会错误地写出下面这种版本:
java
if (count == 0) {
candidate = num;
count = 0; // ❌ 致命错误!
}
这段代码的问题是:
当 count == 0 且选中新候选人时,却不给它加上当前这一票。
结果是:
- count 一直保持为 0
- 根本没有进行投票累积
- 每次都只是不断更换候选人
- 于是算法最终返回的是数组的最后一个元素
- 在测试用例
[3,3,4]中错误返回4
正确做法必须是:
java
count = 1; // 当前元素至少贡献一票
这是 Boyer-Moore 算法最关键的地方。
如果这一点写错,整个算法将完全失效。
五、为什么算法可靠?
多数元素出现次数 > n/2,因此:
- 它的票数比所有其他元素加起来还多
- 在"抵消"过程中,成对消掉的都是多数元素与非多数元素的票
- 多数元素永远不可能被完全抵消
- 并且当 count 归零时,多数元素在数组后面部分必然还会再次出现,重新成为 candidate
因此,算法仅需一次遍历即可确定结果。
六、总结
Boyer-Moore 投票算法是一种极其优雅的线性时间算法,特点:
- 时间复杂度:O(n)(单次遍历)
- 空间复杂度:O(1)(仅用两个变量)
- 适用场景:明确存在多数元素
并且务必牢记:
⚠ 实现时 count = 0 分支必须写成 count = 1,否则算法彻底失效。
掌握了这个技巧,许多相关问题(比如"找出现超过 n/3 的元素"、"找出现 k 次的元素")都能顺藤摸瓜继续拓展。
另一种思路(更简洁):
因为存在一定出现>n/2次的元素,那么对数组进行排序后,在中间的元素就是要找的多数元素。
java
import java.util.Arrays;
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length/2];
}
}