今天我们继续借助MarsCode优化改进一道简单的算法题。
问题描述
小R从班级中抽取了一些同学,每位同学都会给出一个数字。已知在这些数字中,某个数字的出现次数超过了数字总数的一半。现在需要你帮助小R找到这个数字。
测试样例
样例1:
输入:
array = [1, 3, 8, 2, 3, 1, 3, 3, 3]
输出:3
样例2:
输入:
array = [5, 5, 5, 1, 2, 5, 5]
输出:5
样例3:
输入:
array = [9, 9, 9, 9, 8, 9, 8, 8]
输出:9
第一步,我们使用最直观的写法。
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
// 函数:查找出现次数超过数组总数一半的数字
int solution(const vector<int>& array) {
unordered_map<int, int> count_map; // 用于存储数字及其出现次数
int majority_count = array.size() / 2; // 计算超过一半的阈值
// 遍历数组,统计每个数字的出现次数
for (int num : array) {
count_map[num]++;
// 如果当前数字的出现次数超过了一半,返回该s数字
if (count_map[num] > majority_count) {
return num;
}
}
// 如果没有找到超过一半的元素,返回-1
return -1;
}
- 数据结构选择:使用
unordered_map,它允许我们以O(1)的平均时间复杂度存储和检索数字的出现次数。 - 遍历数组:通过遍历输入数组,将每个数字及其出现次数记录在哈希表中。
- 检查条件:在统计每个数字的出现次数时,立即检查该数字的出现次数是否超过了数组长度的一半。如果超过,则返回该数字。
- 结果返回:如果遍历完成后没有找到任何数字出现次数超过一半,返回-1。
缺点是使用了O(n)级别的额外空间,十分不划算!
那怎么改进呢?
试想有这样一种情形,在一个遥远的村庄里,动物们决定选出新的村长。为了快速找到大家心目中的“最佳村长”,聪明的老乌龟提出使用一种投票法。投票开始时,老乌龟设定了一个候选者和一个计数器。
每当动物投票时,老乌龟根据以下规则进行:
- 如果计数器为0,就将候选者设为当前投票动物,并将计数器设为1。
- 如果当前投票动物与候选者相同,计数器加1。
- 如果不同,计数器减1。
投票结束后,老乌龟统计候选者的得票数,发现兔子获得了超过一半的支持。于是,兔子被选为新村长,承诺会用心管理村庄。这个投票方法被称为摩尔投票。
把它迁移到这道题上。
#include <iostream>
#include <vector>
using namespace std;
// 函数:查找出现次数超过数组长度一半的元素
int Solution(const vector<int>& array) {
int candidate = 0; // 候选元素
int count = 0; // 计数器
// 第一轮遍历:进行投票以确定候选元素
for (int num : array) {
if (count == 0) {
candidate = num; // 更新候选者
count = 1; // 重置计数器
} else if (num == candidate) {
count++; // 候选者相同,计数加一
} else {
count--; // 候选者不同,计数减一
}
}
// 第二轮遍历:验证候选元素的出现次数
int actual_count = 0;
for (int num : array) {
if (num == candidate) {
actual_count++; // 统计候选者的出现次数
}
}
// 检查候选元素是否真的超过一半
if (actual_count > array.size() / 2) {
return candidate; // 返回有效的候选元素
} else {
return -1; // 返回-1表示没有找到有效的元素
}
}
-
功能:
- 该代码实现了摩尔投票算法,用于寻找给定整数数组中出现次数超过一半的元素(多数元素)。
- 通过两轮遍历,第一轮确定候选者,第二轮验证该候选者是否真的出现超过一半的次数。
-
时间复杂度:
- 时间复杂度为O(n),其中n是数组的长度。因为代码只需遍历数组两次。
-
空间复杂度:
- 空间复杂度为O(1),只使用了常量级别的额外空间(不依赖于输入数组大小)。
可以,空间复杂度极大的减小了。
但有一些缺点缺乏通用性。
- 该算法仅适用于寻找出现次数超过一半的元素,对于其他类型的问题(如查找频率最高的元素)则不适用。
不过这道题不受影响,还可以继续优化吗?
或还有没有其它好的解法
有的,这种思路是基于位操作的。具体来说,你可以将所有的数转换为二进制形式,然后逐位统计所有数在每一位上1的数量和0的数量。
方法描述:
-
统计每一位的1的数量:
- 对于数组中的每一个整数,都将其转换为二进制形式,统计每一位上1的数量。
-
比较每一位的数量:
- 对于每一位,比较统计到的1的数量和0的数量。如果某一位上1的数量超过了数组长度的一半,则该位的结果是1;否则结果是0。
-
构建结果:
- 最后,将得到的二进制位合并起来,得到最终的众数。
#include <iostream>#include <vector>using namespace std;
int find_majority_element(const vector<int>& array) {
int n = array.size();
int result = 0; // 遍历32位整数的每一位
for (int bit = 0; bit < 32; bit++) {
int count = 0; // 统计当前位1的数量
for (int num : array) {
if (num & (1 << bit)) {
count++;
}
} // 比较1的数量与0的数量
if (count > n / 2) {
result |= (1 << bit); }
}
return result;
}
- 空间复杂度:相比于使用哈希表或其他数据结构,该方法只使用了常量级的空间O(1)。
- 时间复杂度:该方法的时间复杂度为O(n),与摩尔投票法相同。
- 对负数处理:在处理负数时,二进制表示可能需要特殊处理(如补码表示)。
最后,我们提交结果,今天的任务完成了。