找数组中的Majority Element,Majority Element的定义见下,对应着LeetCode上的两道题,直接看题:
LeetCode--169. Majority Element
给定一个长度为n的数组,找出其中的Majority Element。其中Majority Element的定义为数组中出现次数大于 n / 2次的数字。
解决这个问题有以下几种思路:
1、暴力法
遍历数组中每一个元素,统计其出现的系数是否大于 n/2次,如果是就直接返回。时间复杂度o()。空间复杂度o(1);
参考代码:
class Solution {
public:
int majorityElement(vector<int>& nums) {
int i;
for(i = 0;i < (int)nums.size();++i) {
if(count(nums.begin(), nums.end(), nums[i]) > nums.size() / 2) { //这里使用了STL函数,count函数也会遍历整个数组,所以时间复杂度为o(n2)
break;
}
}
return nums[i];
}
};
注:题目中说道了,保证给定的数组中一定存在着Majority Element,所以上面那样写是没有问题的,外层的for循环不会遍历完,也就是变量i不会增加到nums.size()再退出循环,如果这样的话nums[i]的下标就越界了,当然题目保证了这种情况不会发生。但是上面的代码是不能AC的,当测试集很大时,该算法超时!!
2、排序。
对输入数组先排序,那么 索引 n/2 处对应的元素一定是Majority Element,直接返回即可。时间复杂度o(nlgn),空间复杂度o(1)。
AC代码:
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(), nums.end());
return nums[nums.size() / 2];
}
};
3、随机法。
由于Majority Element出现的次数大于 n/2那么每次随机从nums中选取一个数有大于1/2的概率选中Majority Element,所以我们可以随机选取一个索引,然后统计其出现的次数是否大于n/2,如果是就直接返回索引处对应的值,否则继续迭代。数学期望是最多循环两次就可以得到结果了,所以时间复杂度为o(n),空间复杂度为o(1)。
AC代码:
class Solution {
public:
int majorityElement(vector<int>& nums) {
int res = 0;
srand(time(NULL));
while(true)
{
int n = rand() % nums.size();
res = nums[n];
if(count(nums.begin(),nums.end(),res) > (nums.size() >> 1) )
break;
}
return res;
}
};
4、分治。
每次将数组一分为二,分别统计左右两边的Majority Element,基准条件是被分成的数组只有一个元素了,那么该元素就是Majority Element,直接返回就可。如果左右两边的Majority Element相同,返回它即可,如果不相同则返回出现次数更多的那一个。
AC代码:原代码出处
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;
}
};
时间复杂度分析:类似于归并排序,如果复杂度为T(n),显然有 T(n) = 2T(n/2) + o(n)。所以最终的时间复杂度为o(nlgn)。由于递归函数的调用,消耗的空间复杂度为o()。n为数组长度。
5、Boyer-Moore算法。
代码分为两步:1、找出一个Majority Element候选值。2、检查该候选值是否是Majority Element。由于在本题中题目已经明确存在Majority Element了,所以第二步可以省略。
找出候选者的方法:
初始化候选值为数组中的任一元素(为了方便直接初始化为首元素),初始化变量count为1。遍历数组,如果等于候选值,count++,或者count--。当count == 0时,给候选值赋值为当前遍历值。
In python:
candidate = 0
count = 0
for value in input:
if count == 0:
candidate = value
if candidate == value:
count += 1
else:
count -= 1
In C++ AC代码:
class Solution {
public:
int majorityElement(vector<int>& nums) {
int res = 0;
int count = 0;
for(int i = 0;i < nums.size();++i)
{
if(count == 0) {
res = nums[i];
}
if(nums[i] == res) {
count++;
} else {
count--;
}
}
return res;
}
};
时间复杂度o(n),空间复杂度O(1)。
上面就是该题的一些解法思路,在这里还提高了一种使用位运算计算的代码,该思想与博主曾写过的一篇位运算总结里面的--给你一个非空的整数数组,里面只有一个数只出现了一次,其余的数都出现了两次,输出这个这出现一次的数字,的通用解法类似,感兴趣的可以去看看。
LeetCode---229. Majority Element II
与上一题不同的是,本题是找出出现次数大于 n/3 次的数。易知,一个数组中出现n/3次的数可能有一个也可能有两个。本题使用的算法是基于Boyer-Moore算法的扩展。
AC代码:
class Solution {
public:
vector<int> majorityElement(vector<int>& nums) {
vector<int> res;
if(nums.empty()) {
return res;
}
int count1 = 0;
int count2 = 0;
int candidate1 = nums.front();
int candidate2 = nums.front();
for(int i = 0;i < (int)nums.size();++i) {
if(nums[i] == candidate1) {
count1++;
} else if(nums[i] == candidate2) {
count2++;
} else if(count1 == 0) {
candidate1 = nums[i];
count1 = 1;
} else if(count2 == 0) {
candidate2 = nums[i];
count2 = 1;
} else {
count1--;
count2--;
}
}
if(count(nums.begin(),nums.end(),candidate1) > nums.size() / 3) {
res.push_back(candidate1);
}
if(count(nums.begin(),nums.end(),candidate2) > nums.size() / 3 && candidate1 != candidate2) {
res.push_back(candidate2);
}
return res;
}
};
可以发现使用这用思路可以线性时间内查找出现 n/k 次的元素,只需要再增加几个候选项即可。
参考:
JAVA-------------------Easy Version To Understand!!!!!!!!!!!!
Majority Voting Algorithm Find the majority element in a list of values