169.多数元素
这个题是个简单题,但是它有很多种做法,值得我们好好思考.
法一: hash法
这个方法应该是最容易想到的,在一次遍历中记录nums里面的元素出现的次数,并与保存的当前最多出现次数的元素进行比较. 时间复杂度O(n) 空间复杂度O(n)
class Solution {
public:
int majorityElement(vector<int>& nums) {
unordered_map<int,int> map1;
int max_count=0,ans=0;
for(int num:nums){
map1[num]++;
if(map1[num]>max_count){
max_count=map1[num];
ans=num;
}
}
return ans;
}
};
法二: 随机法
这是leetcode中少见的不"负责"做法,理论上最大时间复杂度可达O(∞),但是我们对它的期望进行估计为O(n),空间复杂度为O(1). 具体做法为随机取一个数,进行一次遍历验证看看它是不是多数元素(出现次数大于等于n/2+1),因为题目保证有结果,所以每次随机的正确可能性还是很大的,其期望大于恰巧有一般是同一个元素的情况,而后者的时间复杂度为1/2+1/2*1/2*2+1/2*1/2*3+...(1/2)^i*i,代表第一次取得该元素,第二次取得该元素...第i次取得该元素,i从1到正无穷.求和为2.
法三: 排序法
因为多数元素是出现次数大于等于n/2下取整+1的元素,所以有一个显见的结论就是如果这些个多数元素放在一起,那么数组的中位数一定是所求,因为数组的中位数距离数组开头和数组结尾都是n/2下取整,所以我们可以排序后返回中位数.
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(), nums.end());
return nums[nums.size()/2];
}
};
法四: 分治法
我们要注意到一个事实,就是如果我们把数组分成长度最接近的两部分,mid=(i+j)/2,那么原数组的多数元素一定是这两部分的多数元素中的某一个,如果两者相等,那么显然就是它了,而不相等的时候我们要判断到底是哪一个,那么我们只用看看谁在这个区间里面出现的次数>=len/2+1即可
边界条件:当递归到一个元素时,显然它就是多数元素了.
时间复杂度 T(n)=2T(n/2)+O(n),有主定理知,时间复杂度为O(nlogn),空间复杂度O(logn)为栈的空间开销.
注: 这里有一个致命的tips: 如果在递归中,我们的vector参数不使用引用类型&,而是使用普通参数类型,就会超时,死得很惨
class Solution {
public:
int majorityElement(vector<int>& nums) {
return find_it(0,nums.size()-1,nums);
}
int find_it(int i,int j,vector<int>& nums){
if(i==j) return nums[i];
if(i>j) return -1;
int mid=(i+j)/2;
int left=find_it(i,mid,nums);
int right=find_it(mid+1,j,nums);
if(left==right) return left;
else if(count_in_range(left,i,j,nums)>((j-i+1)/2))
return left;
else return right;
}
int count_in_range(int target,int i,int j,vector<int>& nums){
int cnt=0;
for(int k=i;k<=j;k++)
if(nums[k]==target)
cnt++;
return cnt;
}
};
可以自行尝试去掉&引用的情况.
方法五: Boyer-Moore 投票算法
这个方法是所有方法中最好的,不仅操作简单,而且时间复杂度O(n)空间复杂度O(1).
简单地说就是假设多数元素为ans,我们把不等于ans的和ans成对删除,这样最后留下的一定是ans
先来看一看它的具体操作: 我们维护一个candidate,和一个count,candidate表示候选人,也就是在当前我们认为的所求,count表示当前元素支持candidate的人数,在开始时candidate任定,count=0;而每次遇见和candidate相等的nums[i],count++,表示有一个人支持candidate,而每次遇见不相等的元素,count--,表示又少了一个人支持candidate,当count为0时,重新选择下一个人为candidate,直到nums的末尾,candidate即为所求.
再来看一看它的正确性说明: 假设多数元素为ans,那么candidate一共有两种情况,第一种情况也就是candidate等于ans,那么因为ans在数组nums里面个数大于等于一半+1,所以一定会被选上,第二种情况就是candidate不等于ans,那么这个时候candidate一定不可能坚持到数组末尾,因为反对的个数太多了,而当count=0时我们就重新选择candidate了,最后一定会选择到ans,因为不同意的都被ans"干掉"了
class Solution {
public:
int majorityElement(vector<int>& nums) {
int candidate=nums[0],count=0;
for(int i=0;i<nums.size();i++){
if(nums[i]==candidate) count++;
else count--;
if(count==0&&i<nums.size()-1) candidate=nums[i+1];
}
return candidate;
}
};