【很优秀的分析】LeetCode169:Majority Element 229. Majority Element II

转载的链接点击打开链接 帮助很大十分感谢。

Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋ times.

You may assume that the array is non-empty and the majority element always exist in the array.

Credits: 
Special thanks to @ts for adding this problem and creating all test cases.

Hide Tags Divide and Conquer Array Bit Manipulation

这道题在剑指offer上见到过,使用一次遍历的方式就可以求解。做完以后看了下Discuss,发现还有好多种解法。主要有下面这么多种,图片来自于这里

这里写图片描述

我针对其中的5种进行了实现,第1种由于时间复杂度太高没有实现,第4种由于存在最差的情况所以也没有实现。


解法一:Hash table

这种解法是使用一个hash表,键用来存放数组的元素,键对应的值存放元素出现的次数。遍历整个数组,查找它在hash表中是否出现,如果出现将出现次数加1,如果没有出现,将它插入hash表中,并设置它的出现次数为1。每次遍历到一个元素,判断它的出现次数是否超过了数组长度的一半,要是超过了就返回该元素。时间复杂度是O(n),空间复杂度是O(n)。。 
runtime:48ms

     int majorityElement(vector<int>& nums) {
            if(nums.size()==1)
                return nums[0];
            map<int,int> tables;
            for(int i=0;i<nums.size();i++)
            {
                if(tables.count(nums[i]))
                {
                    tables[nums[i]]++;
                    if(tables[nums[i]]>nums.size()/2)
                        return nums[i];
                }
                else
                {
                    tables[nums[i]]=1;
                }
            }


     }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

解法二:Sorting

这种解法其实应该一开始就想到的,因为这种解法可以说是最简单的。对数组进行排序,那么出现次数超过一半的元素必定是数组中的中间元素,返回这个元素即可。时间复杂度是O(nlogn),空间复杂度是O(1)。 
runtime:40ms

    int majorityElement(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        return nums[nums.size()/2];
    }
  • 1
  • 2
  • 3
  • 4

解法三:Divide and conquer

这道题的提示是使用分治法或位操作来求解,最开始我思考时也是把数组分成分成两部分,但是在原始数组中出现次数超过一半的元素不一定在子数组中出现次数超过一半,到这里就不知道如果进行后续的操作了。看了上面的解释发现正确的思考方法应该是这样的: 
将数组分成两部分,寻找第一个部分中出现次数超过一半的元素为A,第二个部分出现次数超过一半的元素为B,如果A==B,那么A就是这个数组中出现次数超过一半的元素,如果A!=B,那么A和B都可能是这个数组中出现次数超过一半的元素,那么重新遍历这个数组,记录A和B出现的次数,返回出现次数多的元素,时间复杂度T(n)=2T(n/2)+2n=O(nlogn)。

runtime:20ms

int majorityElement(vector<int>& nums) {

         return helper(nums,0,nums.size()-1);
     }

     int helper(vector<int> &nums,int begin,int end)
     {
         if(begin==end)
            return nums[begin];
         if(begin<end)
         {
             int mid=begin+(end-begin)/2;
             int left=helper(nums,begin,mid);
             int right=helper(nums,mid+1,end);
             if(left==right)
                return left;
             else
             {
                 int leftCount=0;
                 int rightCount=0;
                 for(int i=begin;i<=end;i++)
                 {
                     if(nums[i]==left)
                        leftCount++;
                     else if(nums[i]==right)
                        rightCount++;
                 }

                if(leftCount<rightCount)
                        return right;
                else if(leftCount>rightCount)
                        return left;
             }
         }
     }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

解法四:Moore voting alogrithm

这种解法就是在《剑指offer》上看到的那种解法。它本质上也是一种分治法,只不过在编程时使用了一些技巧,结果没那么容易看出来了。 
算法的思想如下:每次从数组中找出一对不同的元素,将它们从数组中删除,直到遍历完整个数组。由于这道题已经说明一定存在一个出现次数超过一半的元素,所以遍历完数组后数组中一定会存在至少一个元素。 
上面就是这种算法的思想,删除操作可以在常数时间内完成,但是查找不同的元素无法在常数时间内完成,这里有一个技巧。 
在算法执行过程中,使用常量空间来记录一个候选元素c以及它的出现次数f(c),c即为当前阶段出现次数超过一半的元素。在遍历开始之前,该元素c为空,f(c)=0。然后开始遍历数组A时:

  1. 如果f(c)为0,表示当前并没有候选元素,也就是说之前的遍历过程中没有找到超过一半的元素。那么,如果超过一半的元素c存在,那么c在剩下的子数组中,出现的次数也一定超过一半。因次我们可以将原始问题转化成它的子问题。此时c赋值为当前元素,同时f(c)=1。
  2. 如果当前A[i]==c,那么f(c)+=1。(没有找到相同的元素,只需要把相同的元素累加起来)
  3. 如果当前元素A[i]!=c,那么f(c)-=1(相当于删除一个c),不对A[i]做任何处理(相当于删除A[i]) 
    如果遍历结束之后,f(c)不为0,那么再次遍历一遍数组,如果c真正出现的频率,上面算法的时间复杂度是O(n),空间复杂度为O(1)。这种方法的分析来自这里

runtime:20ms

    int majorityElement(vector<int>& nums) {
        int count=0;
        int result=nums[0];
        for(int i=0;i<nums.size();i++)
        {
            if(count==0||nums[i]==result)
            {
                count++;
                result=nums[i];
            }
            else
                count--;
        }
        return result;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

解法五:Bit manipulation

还可以使用位操作来求解这道题。因为一个整数在32为机器上只有32位,那么可以使用一个长度为32的数组来记录输入数组中每1位中1的个数和0的个数,由于存在一个出现次数超过一半的元素,那么取第i位中出现次数多的(0或者1)即可以构成超过数组一半元素。 
runtime:40ms

  int majorityElement(vector<int>& nums) {
        int **table=new int*[32];
        int result=0;
        for(int i=0;i<32;i++)
        {
            table[i]=new int[2]();
        }
        for(int i=0;i<nums.size();i++)
        {
            for(int j=0;j<32;j++)
            {
                int pos=1<<j&nums[i]?1:0;
                table[j][pos]++;
            }
        }
        for(int i=0;i<32;i++)
        {
            if(table[i][1]>table[i][0])
                result+=1<<i;
        }
        return result;
    }

II(hash会超时)(摩尔投票法---不想学也得会的方法)

这道题让我们求出现次数大于n/3的众数,而且限定了时间和空间复杂度,那么就不能排序,也不能使用哈希表,这么苛刻的限制条件只有一种方法能解了,那就是摩尔投票法 Moore Voting,这种方法在之前那道题Majority Element 求众数中也使用了。题目中给了一条很重要的提示,让我们先考虑可能会有多少个众数,经过举了很多例子分析得出,任意一个数组出现次数大于n/3的众数最多有两个,具体的证明我就不会了,我也不是数学专业的。那么有了这个信息,我们使用投票法的核心是找出两个候选众数进行投票,需要两遍遍历,第一遍历找出两个候选众数,第二遍遍历重新投票验证这两个候选众数是否为众数即可,选候选众数方法和前面那篇Majority Element 求众数一样,由于之前那题题目中限定了一定会有众数存在,故而省略了验证候选众数的步骤,这道题却没有这种限定,即满足要求的众数可能不存在,所以要有验证。代码如下:

class Solution {
public:
    vector<int> majorityElement(vector<int>& nums) {
        //nums={1,3,3,4};
        int len=nums.size();
        vector<int> ans;
        if(len<=0) return ans;
        if(len==1) return nums;
        int m=nums[0];
        int n=nums[1];
        int cm=0;
        int cn=0;
        for(int i=0;i<=len-1;i++)
        {
            if(nums[i]==m) cm++;
            else if(nums[i]==n) cn++;
             else if(cm==0) 
            {
                m=nums[i];
                cm++;
            }
            else if(cn==0)
            {
                n=nums[i];
                cn++;
            }
            else 
            {
                cm--;
                cn--;
            }
          // printf("%d %d %d %d\n",m,cm,n,cn); 
        }
        cn=cm=0;
        for(int i=0;i<=len-1;i++)
        {
            if(nums[i]==m) cm++;
            else if(nums[i]==n) cn++;
        }
        if(cm>len/3) ans.push_back(m);
        if(cn>len/3) ans.push_back(n);
        return ans;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值