1.题目链接
2.题目描述
给定一个大小为 n
的数组 nums
,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋
的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
cpp
输入:nums = [3,2,3]
输出:3
示例 2:
cpp
输入:nums = [2,2,1,1,1,2,2]
输出:2
提示:
cpp
n == nums.length
1 <= n <= 5 * 10^4
-109 <= nums[i] <= 10^9
3.解题思路
为了追求效率,让时间复杂度和空间复杂度都保持在最低,我们使用摩尔投票算法来实现这道题。
摩尔投票算法是一种用于在一个数组中找出出现次数超过一半的元素(即众数)的算法,核心思路就是通过 "抵消" 不同元素的出现次数,最终剩下的元素就是多数元素,因为多数元素的出现次数超过数组长度的一半,所以无论其他元素如何抵消,最终剩下的一定是多数元素。
这道题要求返回该数组中的最多元素,也就是众数,我们使用一个候选元素和一个计数器来找出数组中的众数。首先,定义一个变量 num 作为候选众数,初始值为0;再定义一个变量 cnt 来记录候选元素的票数,初始值为0。接下来,我们通过遍历数组 nums 中的每个元素 x。如果 cnt 为0,说明当前没有有效的候选元素,那么我们将 x 作为新的候选众数,且将 cnt 设为1。若 x 等于当前的候选元素 num,则票数 cnt 增加1,表明该元素得到更多支持;若 x 与候选元素不同,则票数 cnt 减少1,表明候选元素的支持度下降。最终,当遍历完成时,num 即为数组中的众数。
通过这种方式,我们只需要遍历一次数组,通过巧妙地更新候选元素和票数,能够在 O(n) 的时间复杂度内找出数组中出现次数超过一半的元素。

4.题解代码
cpp
class Solution {
public:
int majorityElement(vector<int>& nums)
{
int num = 0;//定义一个变量num,用于存储候选中的众数
int cnt = 0;//定义一个变量cnt,用于储存候选元素的票数(出现次数)
for (int x : nums)//循环,依次取出数组nums中的变量,赋给x
{
if (cnt == 0)//如果此时候选元素票数为0,那么更新候选元素为当前元素
{
num = x;//将当前元素的值赋给候选元素
cnt++;//让候选元素票数+1
}
else if (x = num)//如果候选元素和当前遍历的元素相同
{
cnt++;//那么就让候选元素得票数+1
}
else//如果候选元素和当前遍历的元素不同
{
cnt--;//那么就让候选元素得票数-1
}
}
return num;//最终返回num的值,即为该数组中的最多元素
}
};
5.示例演算
步骤 | 遍历的元素 x |
条件判断 | num (候选元素) |
cnt (计数) |
说明 |
---|---|---|---|---|---|
初始 | 无 | 无 | 0 | 0 | 初始状态 |
1 | 2 | cnt == 0 成立 |
2 | 1 | 首次遇到元素,设为候选 |
2 | 2 | x == num (2 == 2) |
2 | 2 | 相同元素,计数 + 1 |
3 | 1 | x != num (1 != 2) |
2 | 1 | 不同元素,计数 - 1 |
4 | 1 | x != num (1 != 2) |
2 | 0 | 不同元素,计数 - 1(抵消) |
5 | 1 | cnt == 0 成立 |
1 | 1 | 计数为 0,更换候选为 1 |
6 | 2 | x != num (2 != 1) |
1 | 0 | 不同元素,计数 - 1(抵消) |
7 | 2 | cnt == 0 成立 |
2 | 1 | 计数为 0,更换候选为 2 |
结束 | 无 | 循环结束 | 2 | 1 | 返回最终候选元素 2 |
6.复杂度计算
时间复杂度:只将数组中的元素遍历一次,故时间复杂度为O(n)。
空间复杂度:只使用了常数空间来储存变量,没有使用额外空间,故时间复杂度为O(1)。
7.拓展:哈希表法和排序法
哈希表法
我们使用一个哈希表 counts 来记录每个数字的出现次数。首先,初始化 majority 变量为0,表示当前的候选众数,同时初始化 cnt 为0,表示当前候选众数的出现次数。然后,我们遍历数组 nums 中的每一个元素 num。对于每个元素,首先更新哈希表中该元素的出现次数 counts[num]。接着,我们判断当前元素的出现次数是否大于目前最大出现次数 cnt,如果是,说明该元素成为新的众数,我们更新 majority 为当前元素,cnt 更新为该元素的出现次数。最后,遍历结束后,majority 变量中保存的就是数组中出现次数最多的元素。通过这种方式,我们能够高效地找出众数,且时间复杂度为 O(n),空间复杂度为 O(n)。
哈希表法代码:
cpp
class Solution {
public:
int majorityElement(vector<int>& nums) {
// 使用哈希表记录每个数字的出现次数
unordered_map<int, int> counts;
// 初始化 majority 为 0, 用于保存当前候选的众数
// 初始化 cnt 为 0, 用于记录当前众数的出现次数
int majority = 0, cnt = 0;
// 遍历数组 nums 中的每个元素
for (int num: nums) {
// 更新哈希表中当前数字的出现次数
++counts[num];
// 如果当前数字的出现次数大于当前最大出现次数 cnt
if (counts[num] > cnt) {
// 更新众数为当前数字
majority = num;
// 更新当前众数的出现次数为该数字的出现次数
cnt = counts[num];
}
}
// 返回数组中出现次数最多的数字(众数)
return majority;
}
};
排序法
我们首先对数组 nums 进行排序。排序之后,数组中的元素按从小到大的顺序排列,众数必定出现在数组的中间位置。因为根据题意,众数的出现次数超过了数组长度的一半,因此它必定会在排序后的数组的中间位置。具体来说,我们通过访问 nums[nums.size() / 2] 来直接获取这个中间元素,即为数组中的众数。通过这种方式,我们能够在 O(n log n) 的时间复杂度下,快速地找到众数。
排序法代码:
cpp
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(), nums.end()); //对数组进行排序
//由于众数的出现次数超过数组长度的一半,排序后它必定位于中间位置
return nums[nums.size() / 2];//返回排序后的数组中间位置的元素
}
};
三种方法对比
方法 | 核心原理 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
---|---|---|---|---|---|
摩尔投票法 | 元素抵消策略 | O(n) | O(1) | 效率最高,空间最优 | 需保证多数元素存在 |
哈希表法 | 哈希计数 | O(n) | O(n) | 直观易理解,适用性广 | 空间占用高 |
排序法 | 中位数即众数 (因 count>⌊n/2⌋) | O(nlogn) | O(1)或 O(n) | 代码最简洁 | 修改原数组,效率最低 |