Task

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.

Example

  • Example 1:
1
2
Input: [3,2,3]
Output: 3
  • Example 2:
1
2
Input: [2,2,1,1,1,2,2]
Output: 2

Solution

  • 花花酱的5种思路8种实现: leetcode169

用哈希表存储计数

  • 使用哈希表存储每个数出现的次数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Runtime: 44 ms, faster than 62.77% of C++ online submissions for Majority Element.
// Memory Usage: 19.8 MB, less than 6.06% of C++ online submissions for Majority Element.
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int,int> count;
        const int n=nums.size();
        for(const int num:nums)
            //递增哈希表中的计数并判断
            if (++count[num]>n/2)
                return num;
        return -1;
    }
};

用平衡搜索树存储计数

  • 仅将哈希表中的unordered_map换成map
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Runtime: 44 ms, faster than 62.77% of C++ online submissions for Majority Element.
// Memory Usage: 19.7 MB, less than 6.06% of C++ online submissions for Majority Element.
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        map<int,int> count;
        const int n=nums.size();
        for(const int num:nums)
            if (++count[num]>n/2)
                return num;
        return -1;
    }
};

随机取一个数字验证

  • 随机在数组中取一个数,由于要求的数字一定占一半以上,故有一半以上的概率取到它。
  • 对随机取的数字测试是否是所求值,若不是则继续采样
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Runtime: 40 ms, faster than 75.16% of C++ online submissions for Majority Element.
// Memory Usage: 19.7 MB, less than 6.06% of C++ online submissions for Majority Element.
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        //初始化种子
        srand(time(nullptr));
        const int n=nums.size();
        while(true){
            //取随机数的索引
            const int index=rand()%n;
            const int maj=nums[index];
            int cnt=0;
            for(const int num:nums)
                //当遍历到取的这个数时,计数增加并继续判断是否为majority
                if(num==maj && ++cnt>n/2)
                    return num;
        }
        return -1;
    }
};

位操作,对每一位投票

  • 将每个数看成二进制,依次对每一位遍历整个数组,若一个int数出现的次数大于n/2则其每一位出现的次数也大于n/2,这样可求出每个bit的值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Runtime: 68 ms, faster than 14.31% of C++ online submissions for Majority Element.
// Memory Usage: 19.7 MB, less than 6.06% of C++ online submissions for Majority Element.
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        const int n=nums.size();
        int result=0;
        //对每一位遍历
        for(int i=0;i<32;++i){
            //当前位的mask
            int mask=1<<i;
            int cnt=0;
            for(const int num:nums)
                //对当前位的1计数
                if(num&mask)
                    ++cnt;
            //若当前位的1多于32个,则结果中当前位一定是1
            if(cnt>n/2)
                result|=mask;
        }
        return result;
    }
};

Boyer-Moore投票

  • 先假设结果是某个值,计数初始化为0
  • 遍历数组:
    • 若当前数等于假设值,则增加计数。
    • 若当前数不等于假设值,则减少计数。若计数减为0则说明假设值不是频率大于n/2的数,用当前值更新假设值,并重置计数为1(而不是0,因为不会再遍历到当前数)
  • 遍历完数组后,假设值一定等于频率大于n/2的数,且计数一定大于等于1
  • 由于只需遍历一次数组并只需维护一个计数,故效率较高
  • 该算法的思路是:减而治之
  • 该算法能work的原理是(参见https://zhuanlan.zhihu.com/p/76518429):
    • 假设整个数组是A
    • 由于初始值是第一个数,故刚开始计数必然是+1
    • 假设在某个时刻,计数减为0,此时左侧是P,右侧是A-P,而当前的假设值必占P的一半。此时A的majority number一定是A-P的majority number,缩小了问题的规模
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Runtime: 32 ms, faster than 87.31% of C++ online submissions for Majority Element.
// Memory Usage: 19.7 MB, less than 6.06% of C++ online submissions for Majority Element.
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        //初始化假设值和计数值
        int hyp=nums[0];
        int cnt=0;
        for(const int num:nums){
            //若当前值等于假设值则计数增加
            if(num==hyp)
                ++cnt;
            //若当前值不等于假设值则计数减少,并判断是否减为0
            else if(--cnt==0){
                //减到0时更新假设值和计数值
                hyp=num;
                cnt=1;
            }
        }
        return hyp;
    }
};

直接排序取中位数

  • 排序后,占n/2以上的元素必是中位数
1
2
3
4
5
6
7
8
9
// Runtime: 72 ms, faster than 11.92% of C++ online submissions for Majority Element.
// Memory Usage: 19.6 MB, less than 6.06% of C++ online submissions for Majority Element.
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        return nums[nums.size()/2];
    }
};

部分排序取中位数

  • 类似快排的partition,选定一个pivot位置,排序使得左边都比它小,右边都比它大
  • 使用nth_element实现
1
2
3
4
5
6
7
8
9
// Runtime: 40 ms, faster than 75.16% of C++ online submissions for Majority Element.
// Memory Usage: 19.8 MB, less than 6.06% of C++ online submissions for Majority Element.
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        nth_element(nums.begin(),nums.begin()+nums.size()/2,nums.end());
        return nums[nums.size()/2];
    }
};

分治法

  • 用一个递归函数,每次输入一个序列,将其分为等长的两个子序列,对这两个子序列分别再调用该函数
  • 每次递归时返回众数及其出现次数
  • 每次递归中:
    • 若两个子序列的结果相等,则该结果为所求,返回该值和它在两个子序列中出现的次数之和
    • 若两个子序列的结果不等,则取次数较大的那个值,并统计它在整个序列中出现的次数
 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
// Runtime: 44 ms, faster than 62.77% of C++ online submissions for Majority Element.
// Memory Usage: 19.6 MB, less than 6.06% of C++ online submissions for Majority Element.
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        return majorityElement(nums,0,nums.size()-1).first;
    }
    //递归函数,返回pair表示众数及其出现次数
    pair<int,int> majorityElement(const vector<int> &nums,int l,int r){
        //递归终点
        if(l==r) 
            return {nums[l],1};
        //取中点分治
        int mid=(l+r)/2;
        auto rl=majorityElement(nums,l,mid);
        auto rr=majorityElement(nums,mid+1,r);
        //左右两侧众数相等,直接返回众数和频率
        if(rl.first==rr.first)
            return {rl.first,rl.second+rr.second};
        //左右两侧众数不相等,对两个众数的频率讨论
        if(rl.second>rr.second)
            //若左侧众数频率大,则结果是左侧众数,其出现的频率是左侧的频率(已求出)加右侧的频率(用count统计)
            return {rl.first,rl.second+count(nums.begin()+mid+1,nums.begin()+r+1,rl.first)};
        else
            return {rr.first,rr.second+count(nums.begin()+l,nums.begin()+mid+1,rr.first)};
    }
};