169. Majority Element

来源:互联网 发布:安卓手机网络修复工具 编辑:程序博客网 时间:2024/06/08 00:53

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.

这是一道思路很明确的easy题,基本思路可以有双指针和哈希表两种,都很好想到,但会发现其实都不是最快的算法,没有办法在不开辟更多内存的条件下线性时间内完成。取巧的算法在方法三中给出。

方法一:双指针

我写的第一个就是先排序,将重复数字集中在一起,然后双指针一直向前滑动,判断完一组重复后判断长度,如果大于一半就返回。如下:

int majorityElement(vector<int>& nums) {    sort(nums.begin(), nums.end());    int n = nums.size();    int i, j;    for (i = 0, j = 0; i < n; i++) {        if (nums[i] == nums[j]) continue;        if (i - j > n/2) return nums[j];        else j = i;    }    //最后这个return很必要,因为可能重复的数字出现在数组最后,还没等到下一个不同的数字就结束循环了    return nums[j];}

但这个双指针完全可以再优化一下,我们有一个很重要的先验条件,就是大于数组长度一半的重复,这种重复只可能存在一次,所以我们不用在排序后还挨个判断是否相等,反而可以直接将i, j距离设定为n/2, 保持间隔,找出重复数字出现的地方。如下:

    int majorityElement(vector<int>& nums) {        sort(nums.begin(), nums.end());        int n = nums.size();        int i, j;        for (i = 0, j = i + n/2; j < n; i++, j++) {            if (nums[j] == nums[i]) return nums[j];        }        return -1;    }

这个速度比刚才那个明显快了,毕竟扫描排序后数组的长度缩减了一半。所以除了基本的常用方法外,合理分析题目,充分利用先验条件非常重要。

然而我马上脑子一炸,觉得自己简直耻辱。如果存在这种重复过半的绝对众数,那排序后。。。数组中间那个数一定就是啊!我的天,我也是脑子有坑,还在那双指针个鬼啊。。。

    int majorityElement(vector<int>& nums) {        sort(nums.begin(), nums.end());        int n = nums.size();        return nums[n/2];    }

方法二:哈希表

不用排序,直接将数组中的数值和对应的出现次数建立哈希表,不停插入或更新表,直到发现有数字次数过半就返回。花费了空间保证了线性时间。代码如下:

int majorityElement(vector<int>& nums) {    unordered_map<int, int> myhash;    int n = nums.size();    for (int i = 0; i < n; i++) {        if (++myhash[nums[i]] > (n/2)) return nums[i];    }    return -1;}

代码很简短,效率也比双指针高,毕竟双指针的排序是要O(nlogn),虽然我用的是STL的sort算法,但其实就是先快排然后对于长度小于一定程度使用insertion sort。复杂度就是O(nlogn)。

方法三:最优算法

上面三个都是我自己很快就写出来提交通过的,十分常见。可在leetcode上都没超过50%的提交程序。后来在leetcode讨论区看到了下面这个代码就发现,其实可以更简单。

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

第一眼看到的时候比较懵,没明白这样为什么是对的。后来枚举了几个例子才发现它的正确性。不用排序,不用哈希表。就是利用重复次数超过n/2的特点来做。举个例子,[1, 0, 1, 0, 1], 这个数组里1出现次数过半了,符合题目,应该返回1,这也是过半数字最为分散的情况,但这种情况上述算法也毫无问题。算法里当一个数字第一次出现就将它次数count记为1,下标指针i 不停移动,遇到不同就减一,相等就加一。如果count等于0,重新开始一次加一减一的计算。这个count等于0很有代表性,它代表上一个扫描过的段落里,重复数字和杂项数字(杂项数字就是指那些不同与主流重复数字的那些)数量相等,相互抵消。如果上一个段落里重复数字就是我们要找的那个,没关系,总数大于n/2,所以重复数字总数一定大于杂项数字,现在除去上一个相互抵消的段落,剩下的里面重复还是多于杂项。如果上一个段落里没有我们要找的,更没关系,剩下的里面主流的更是一定大于杂项。就是抱着这种阶段性相互抵消。最后没有被抵消完的一定就是major。
上面是我自己理解的过程,实际上这种算法叫做摩尔投票法,在寻找众数的问题中很有用。

摩尔投票法的基本思想很简单,在每一轮投票过程中,从数组中找出一对不同的元素,将其从数组中删除。这样不断的删除直到无法再进行投票,如果数组为空,则没有任何元素出现的次数超过该数组长度的一半。如果只存在一种元素,那么这个元素则可能为目标元素。

那么有没有可能出现最后有两种或两种以上元素呢?根据定义,这是不可能的,因为如果出现这种情况,则代表我们可以继续一轮投票。因此,最终只能是剩下零个或一个元素。

1 0
原创粉丝点击