在求解众数时,我们常见的方法是哈希统计,时间、空间复杂度是O(n)。有没有优化空间的算法呢?答案是肯定的——摩尔投票法。
在LCR 158. 库存管理 II - 力扣(LeetCode)中,求取元素重复次数大于数组长度一半的元素。常规的哈希表方法不再赘述,这里只谈一下摩尔投票法。
摩尔投票法是基于这样的原理:当一个数的重复次数超过数组长度的一半,每次删除两个不相同的数,最终剩下的即为答案。
定义众数x为第一个元素,遍历数组时等于x则投票数+1,反之则-1。若投票数==0,说明在遍历过的元素中众数的个数等于其余元素,而由于众数在这里被定义成大于数组长度一半的元素,因此剩下没有遍历过的元素中,众数个数一定大于其他元素的。遍历结束,x即为可能答案。由于x的个数可能不到数组长度半(也就是不存在题目定义的“众数”),还需要计算个数。这里使用常数级别的空间复杂度O(1)。
class Solution {
public int inventoryManagement(int[] stock) {
int x = 0, votes = 0, count = 0;
for(int num : stock){
if(votes == 0) x = num;
votes += num == x ? 1 : -1;
}
for(int num : stock)// 验证 x 是否为众数
if(num == x) count++;
return count > stock.length / 2 ? x : 0; // 当无众数时返回 0
}
}
对于类似问题,如,求大于数组长度/3的元素229. 多数元素 II - 力扣(LeetCode),可以参考leetcode官方题解,偷个懒:
class Solution {
public List<Integer> majorityElement(int[] nums) {
int element1 = 0;
int element2 = 0;
int vote1 = 0;
int vote2 = 0;
for (int num : nums) {
if (vote1 > 0 && num == element1) { //如果该元素为第一个元素,则计数加1
vote1++;
} else if (vote2 > 0 && num == element2) { //如果该元素为第二个元素,则计数加1
vote2++;
} else if (vote1 == 0) { // 选择第一个元素
element1 = num;
vote1++;
} else if (vote2 == 0) { // 选择第二个元素
element2 = num;
vote2++;
} else { //如果三个元素均不相同,则相互抵消1次
vote1--;
vote2--;
}
}
int cnt1 = 0;
int cnt2 = 0;
for (int num : nums) {
if (vote1 > 0 && num == element1) {
cnt1++;
}
if (vote2 > 0 && num == element2) {
cnt2++;
}
}
// 检测元素出现的次数是否满足要求
List<Integer> ans = new ArrayList<>();
if (vote1 > 0 && cnt1 > nums.length / 3) {
ans.add(element1);
}
if (vote2 > 0 && cnt2 > nums.length / 3) {
ans.add(element2);
}
return ans;
}
}