2020年12月18日 周五 天气晴 【不悲叹过去,不荒废现在,不惧怕未来】
题目简介
1. 哈希表
class Solution {
public:
int majorityElement(vector<int>& nums) {
unordered_map<int,int> record; // 元素->频率
for(int i=0;i<nums.size();++i)
{
// 边统计个数,边判断是否为众数
record[nums[i]]++;
if(record[nums[i]]>nums.size()/2)return nums[i];
}
return -1;
}
};
- 时间复杂度: O ( n ) O\left( {n} \right) O(n)
- 空间复杂度: O ( n ) O\left( {n} \right) O(n)
2. 排序
由于众数出现的频率大于n/2
,所以在排序之后众数必存在于下标[n/2]
处(本题默认数组中是一定存在众数的),所以返回下标为[n/2]
的元素即可(其实就是返回数组中第[n/2]
大的数)。
2.1 STL库函数sort()
直接使用STL
中的排序函数sort()
。
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(), nums.end());
return nums[nums.size()/2];
}
};
- 时间复杂度: O ( n l o g n ) O\left( {nlogn} \right) O(nlogn)
- 空间复杂度: O ( l o g n ) O\left( {logn} \right) O(logn)
2.2 快速选择算法(quick select)
寻找数组中第[n/2]
大的数就是TopK
问题的变形,我们在 LeetCode 215. 数组中的第K个最大元素(快排、堆排的应用) 详细讨论过,这里使用快速选择算法来解决这个问题。
class Solution {
public:
int majorityElement(vector<int>& nums) {
int len = nums.size();
// 设置随机数种子
srand(time(0));
// 返回升序排列中第 len/2 大的元素
return quickSelect(nums,0,len-1,(len-1)/2);
}
// 基于快排的选择方法(quick select)
int quickSelect(vector<int>& nums, int l, int r, int k){
int rand_idx = rand() % (r-l+1) + l;
swap(nums[l],nums[rand_idx]);
int idx = partition(nums,l,r);
if(idx==k) return nums[idx];
else return idx<k?quickSelect(nums,idx+1,r,k):quickSelect(nums,l,idx-1,k);
}
// 分割操作(以中轴元素为基准,将数组分割成三部分,返回中轴元素索引)
int partition(vector<int>& nums, int l, int r){
int pivot = nums[l]; // 取第一个位置的元素作为中轴元素
// 右边主动接近左边,进行循环覆盖
while(l < r){
// 从右边寻找第一个大于 pivot 的数
while(l < r && nums[r] > pivot) --r;
// 找到后直接进行覆盖
nums[l] = nums[r];
// 从左边寻找第一个小于等于 pivot 的数
while(l < r && nums[l] <= pivot) ++l;
// 找到后直接进行覆盖
nums[r] = nums[l];
}
// 把pivot交换到中间,完成分割
nums[l] = pivot;
return l;
}
};
- 时间复杂度:由于引入了随机化,时间复杂度的期望值是 O ( n ) O\left( {n} \right) O(n)
- 空间复杂度:递归调用的期望深度为 O ( l o g n ) O\left( {logn} \right) O(logn)
注意: 虽然快速选择算法时间复杂度的期望值为 O ( n ) O\left( {n} \right) O(n),但是LeetCode上有很多极端示例,导致算法超时了,无法通过。
3. 摩尔投票
摩尔投票法:遇到相同的数,就投一票,遇到不同的数,就减一票(减到小于0
就更换候选数字),最后还存在票的数就是众数。
- 摩尔投票算法是基于这个事实:每次从序列里选择两个不相同的数字删除掉(或称为“抵消”),最后剩下一个数字或几个相同的数字,就是出现次数大于总数一半的那个。
class Solution {
public:
// 摩尔投票法:遇到相同的数,就投一票,遇到不同的数,就减一票,最后还存在票的数就是众数
int majorityElement(vector<int>& nums) {
int cnt = 0;
int res = 0;
for(int i=0;i<nums.size();++i){
if(res==nums[i]) ++cnt;
else if(--cnt<0){
res = nums[i];
cnt = 1;
}
}
return res;
}
};
- 时间复杂度: O ( n ) O\left( {n} \right) O(n)
- 空间复杂度: O ( 1 ) O\left( {1} \right) O(1)
参考文献
《剑指offer 第二版》
https://leetcode-cn.com/problems/majority-element/solution/duo-shu-yuan-su-by-leetcode-solution/