分治算法刷题

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;
    }
};

  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值