【LeetCode】007 Majority Element 少数服从多数

</pre><p></p><p>【题目】Given an array of size n, find the majority element.The majority element is the element that appears more than. You may assume that the array is non-empty and the majority element always exist in the array.给定一个大小为n的数组,找出其中出现次数超过n/2的元素。(假设其一定存在)</p><p></p><p>【解析】愚蠢如我,程序如下,不解释:</p><p></p><pre name="code" class="cpp">int majorityElement(int* nums, int numsSize) {
    int n = 0;
    for(int i = 0; i < numsSize; i++){
        for(int j = 0;j < numsSize;j++){
            if(nums[i] == nums[j])  n++;
        }
        if(n>numsSize/2)    return nums[i];
        else n = 0;
    }
}


做了一些测试,加上人脑验算了几遍,正确性应该没有问题。然而这种大量消耗时间按复杂度的程序,一律以错误论处。


6 Suggested Solutions in C++ with Explanations

这篇文章中,详细阐释了该题的6种解法,下文将以翻译为主。

文章第一句话就形容了我的算法:

Well, if you have got this problem accepted, you may have noticed that there are 7 suggested solutions for this problem. The following passage will implement 6 of themexcept the O(n^2)brute force algorithm

1. Hash Table 哈希表

哈希表解法是最直接的。我们可以从每个元素及其出现次数中获得一个映射(mapping)。当构建该映射的时候,我们不断更新我们见过的出现最多的的次数。当我们找到出现次数超过n/2时,我们就不需要构建整个映射了。简单地说就是通过映射获得每个元素出现的次数,然后遍历一遍找到最大值。程序如下:

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int, int> counts; 
        int n = nums.size();
        for (int i = 0; i < n; i++)
            if (++counts[nums[i]] > n / 2)
                return nums[i];
    }
};

2. Sorting 排序法

因为出现最多的元素次数超过n/2,那么在一个顺序被排列好的数组中,第n/2个元素一定是出现最多的元素。注意,在排列后的数组nums中,该元素占据了超过n/2个位置。如果第0个元素就是最多元素,那么在第n/2的位置上仍然是它。同理,若最后一个就是最多元素,那么n/2的位置上也仍然是它。

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        nth_element(nums.begin(), nums.begin() + nums.size() / 2, nums.end());
        return nums[nums.size() / 2];
    } 
};
该程序运用了nth_element 函数, 对给定范围[first,last)内的元素进行重新布置,方法是,nth位置的元素放置的值就是把所有元素排序后在nth位置的值。把所有不大于nth的值放到nth的前面,把所有不小于nth的值放到nth后面。

3. Randomization 随机法

该方法运行起来很棒,在online judge上之后16ms的运行时间,几乎是C++解中最快的。

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int n = nums.size();
        srand(unsigned(time(NULL)));
        while (true) {
            int idx = rand() % n;
            int candidate = nums[idx];
            int counts = 0; 
            for (int i = 0; i < n; i++)
                if (nums[i] == candidate)
                    counts++; 
            if (counts > n / 2) return candidate;
        }
    }
};
生成一个0到n的随机数,以此为索引找到数组中对应的元素,数它出现的次数,若大于n/2则输出该元素。由于随机取数,因此每次都有大于二分之一的概率取到多数元素。


4. Divide and Conquer 分治法

应用该方法需要对递归的基础形式有着非常严密的思考。该基础形式是当数列只有一个元素时,那么它就是多数元素。该解耗时24ms。

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        return majority(nums, 0, nums.size() - 1);
    }
private:
    int majority(vector<int>& nums, int left, int right) {
        if (left == right) return nums[left];
        int mid = left + ((right - left) >> 1);
        int lm = majority(nums, left, mid);
        int rm = majority(nums, mid + 1, right);
        if (lm == rm) return lm;
        return count(nums.begin() + left, nums.begin() + right + 1, lm) > count(nums.begin() + left, nums.begin() + right + 1, rm) ? lm : rm;
    }
}; 
定义递归函数majority,输入数列地址引用,最左边的的序号left,最右边的序号right。如果left==right,说明该数组只有一个元素,将其返回。否则进行如下操作,将该数组一分为二。中间元素的编号为mid,代入左右两部分的majority嵌套函数。得到左边的多数元素lm,以及右边的多数元素rm。如果lm==rm,则输出将其值输出。否则,利用count函数分别数左边和右边最多元素的出现次数,将较大者输出。

count函数的用法:count(ivec.begin() , ivec.end() , searchValue)

前两个参数是检索数组的首末地址,第三个参数是被检索数,返回值是该数出现的次数。


5. Moore Voting Algorithm 摩尔投票算法

一个非常机智的办法,同样运行起来非常的块,大约20ms左右。以上算法均由c++实现,其中利用到STL标准模板库,例如nth_element 函数,以及count函数,在c中均不能使用。该方法在不调用这些函数的前提下,仍然可以完成任务,因此我认为这是本体的核心算法。

C++版本:

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int major, counts = 0, n = nums.size();
        for (int i = 0; i < n; i++) {
            if (!counts) {
                major = nums[i];
                counts = 1;
            }
            else counts += (nums[i] == major) ? 1 : -1;
        }
        return major;
    }
};

该算法只对数组进行一次遍历,新建整型变量counts,初始值为0,该变量作为计数器使用。进入循环,从第一个元素开始,如果计数器变量为零,则该元素为待定major元素,counts置1,代表该元素出现了1次。继续进行遍历,当counts不为0,即存在待定major元素,将其与当前num[i]进行比较是否相等,若相等则counts+1,表示该元素又多了一个,若不相等,则counts-1,表示该元素与另一个与之的不同元素抵消掉了。

当counts减为0时,代表待定多数元素在之前的遍历过程中已经抵消完毕,接下来,从当前元素开始继续进行遍历。

该方法的核心在于,将任意两个不相同的元素消除掉,直到数组中只有一种元素(由于多数元素一定存在,因此不存在数为空的情况),该元素就是多数元素。


c语言版本:

int majorityElement(int* nums, int numsSize) {
    int majority = nums[0];
    int count = 1;
    for(int i = 1; i < numsSize; i++){
        if(nums[i] == majority)  count++;
        else if(count == 0){
            majority = nums[i];
            count++;
        }
        else count--;
    }
    return majority;
}

6. Bit Manipulation 位操作

这也是一个非常棒的算法。关键在于每个特定位置“1”的数量。除此以外,你还需要一个第i位为1,其余位为0的蒙版,来获得nums中每一个元素的的第i位。

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int major = 0, n = nums.size();
        for (int i = 0, mask = 1; i < 32; i++, mask <<= 1) {
            int bitCounts = 0;
            for (int j = 0; j < n; j++) {
                if (nums[j] & mask) bitCounts++;
                if (bitCounts > n / 2) {
                    major |= mask;
                    break;
                }
            }
        } 
        return major;
    } 
};
设蒙版mask初始值为1,每次大循环都将其左移一位,一共位移31次(只要数组元素大小不超过2^32-1都可以实现)。在小循环中,利用蒙版,假设为初始值1,则每次检测nums[i]的最后一位是否为1,若是,则bitcounts++,当bitcounts超过半数,说明大多数元素最后一位为1,将major最后一位置1,以此类推。

将每个元素转化为二进制数,从最后一位开始,比较0多还是1多,假设0多,就把剩下的末尾为1的数字删掉,再在末尾为0的数字中进行第二次比较,比较倒数第二位是0多还是1多,删掉该位置0或1较少的元素,以此类推。


你可能感兴趣的:(【LeetCode】007 Majority Element 少数服从多数)