</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时,代表待定多数元素在之前的遍历过程中已经抵消完毕,接下来,从当前元素开始继续进行遍历。
该方法的核心在于,将任意两个不相同的元素消除掉,直到数组中只有一种元素(由于多数元素一定存在,因此不存在数为空的情况),该元素就是多数元素。
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较少的元素,以此类推。