本篇讲述Leetcode里面两个关于找出出现次数最多元素的相似题目,通过多种解法,展现摩尔投票法的魅力。
leetcode #169 Majority Element
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.
翻译为中文:给定一个大小为n的数组,从中找出出现次数大于n/2的元素。假设数组是非空的,并且一定存在次数大于n/2的元素。
- 摩尔投票法
摩尔投票法的基本思想很简单,在每一轮投票过程中,从数组中找出一对不同的元素,将其从数组中删除。这样不断的删除直到无法再进行投票,如果数组为空,则没有任何元素出现的次数超过该数组长度的一半。如果只存在一种元素,那么这个元素则可能为目标元素。本题已经假定一定存在次数大于n/2的元素,那么可知这样的元素只有一个。
class Solution{
public:
int majorityElement(vector<int>& nums) {
int ans, cnt = 0;
for(int n: nums){
if (cnt == 0 || n == ans){
cnt++; ans = n;
}
else
cnt--;
}
return ans;
}
};
需要注意的是,此种解法是建立在一定存在次数大于n/2的元素这一假定上的。如果离开了这个假定,代码这样写是错的,还需要调整。
- 使用map
#include<iostream>
#include <vector>
#include <map>
#include <utility>
using namespace std;
int majorityElement(vector<int>& nums) {
map<int,int> temp;
int len=nums.size();
int result;
if(len==1)
{
result=nums[0];
return result;
}
for(int i=0;i<len;i++)
{
if(temp.count(nums[i])==0)
{
temp.insert(make_pair(nums[i],1));
}
else
{
temp[nums[i]]++;
}
}
pair<int,int>max_num(temp.begin()->first,temp.begin()->second);
for(map<int,int>::iterator i=temp.begin();i!=temp.end();i++)
{
if(i->second>max_num.second)
{
max_num.first=i->first;
max_num.second=i->second;
}
}
result=max_num.first;
return result;
}
int main()
{
vector<int> vec;
vec.push_back(2),vec.push_back(2);
cout<<majorityElement(vec)<<endl;
}
这个程序实现的思路很直接,没有摩尔投票法那么有技巧。而且选出的是次数最多的一个,并没有局限在一定有次数大于n/2这个假定上。当所有元素都只出现一次时,返回的是第一个元素,一些说明性的文字在代码中省略了。
- 普通数组
#include<iostream>
using namespace std;
void majorityelement(int *,int );
void main(){
int size;
cout << "input size : " << endl;
cin >> size;
int *ar = new int[size];
cout << "input members : " << endl;
for (int i = 0; i < size; i++){ //输入数组数据
cin >> ar[i];
}
majorityelement(ar, size); //调用功能函数
cin.get();
delete[]ar; //销毁从堆中申请的数组内存
}
void majorityelement(int *c,int ssize){
int * d = new int[ssize]; //d矩阵为记录次数的矩阵
for (int i = 0; i < ssize; i++){
d[i]=1;
}
//计算每个元素出现次数(比如矩阵[2 4 5 4 4 2]记录次数为[2 3 1 2 1 1],只用看2,4,5第一次出现次数)
for (int i = 0; i < ssize-1; i++){
for (int j = i + 1; j < ssize; j++){
if (c[i] == c[j]){
d[i]++;
}
}
}
int maxTimes = d[0];
int pos=0;
for (int i = 1; i < ssize; i++){ //将次数最多的元素的次数排在最前面
if(maxTimes<d[i]){
maxTimes=d[i];
pos=i;
}
}
if (d[0] <= ssize / 2) { //测试最大次数是否过半
delete[] d;
cout << "Sorry,没有次数过半的元素";
}
else{
delete[] d;
cout << "次数过半的元素是 :" << endl <<c[pos];
}
}
这个算法的思路是很简单暴力的。主要是计算每个元素的“伪次数”(不是严格上的每个元素的次数),并且把次数最高的排在数组最前面。计算次数时用了双重for循环(虽然每次内循环时元素变少),这导致此代码效率不高。代码没有局限在一定存在n/2这个假定上。
讲到摩尔投票法,还有一个题目可以show一下,通过这个题目可以充分体现它的巧妙。
leetcode #229 Majority Element II
Given an integer array of size n, find all elements that appear more than ⌊ n/3 ⌋ times. The algorithm should run in linear time and in O(1) space.
Hint:
How many majority elements could it possibly have?
题目的意思是找出数组中次数超过1/3的元素。要求在线性时间和o(1)空间复杂度完成。
使用和上题相似思路,将每三个不同的元素删去,超过n/3次数的(如果有的话)就在最后剩下里面。
class Solution {
public:
vector<int> majorityElement(vector<int>& nums) {
int cnt1=0,cnt2=0,a=0,b=0;
for(int i:nums){
if(i==a)
++cnt1;
else if(i==b)
++cnt2;
else if(cnt1==0){
a=i;
++cnt1;
}
else if(cnt2==0){
b=i;
++cnt2;
}
else{
--cnt1;
--cnt2;
}
}
cnt1=cnt2=0;
for(int i:nums){
if(i==a)
++cnt1;
else if(i==b)
++cnt2;
}
vector<int> result;
if(cnt1>nums.size()/3) result.push_back(a);
if(cnt2>nums.size()/3) result.push_back(b);
return result;
}
};