每日一题:求数组中出现次数超过一半的元素

求数组n中出现次数超过一半的数

这个题目有非常多种的思路可以解决,我们现在一个一个来看:

思路1:对于每一个数字,枚举数组中所有的数字,统计有多少个数字跟它是一样的,如果超过了一半,就直接break并输出它。

for (int i=1;i<=n;i++)
{
	int num=0;
	for (int j=1;j<=n;j++)
		if (arr[i]==arr[j])
			num++;
	if (num>n/2) 
	{
		cout<<arr[i]<<endl;
		break;
	}
}
这样子做,时间复杂度是O(N*N),空间复杂度是O(N),很好想的到,但并不是一个非常好的思路。


思路2:利用C++中的map,快速的统计每一个数字出现的次数,每当读入一个数字的时候,map[arr[i]]++,直接统计出每一个数字出现的次数:

map<int,int> vis;
for (int i=1;i<=n;i++)
{
	vis[arr[i]]++; //vis保存的是每一个数字出现的次数,vis[5]=6表示5出现了6次
	if (vis[arr[i]]>n/2)
	{
		cout<<arr[i]<<endl;
		break;
	}
}
这个做法,时间复杂度是O(N*log(N)),这里log(N)是每次用map查找一个数字所用到的时间。空间复杂度也是O(N),但是比思路1略大一点。


思路3:利用中位数的性质。一个数字出现次数超过了一半,那么他们的中位数必然就是这个数字,因此可以利用中位数来求。最简单的办法就是,排序以后,直接输出最中间的那个数字。

sort(arr+1,arr+n+1);
cout<<arr[(n+1)/2];
需要注意的是,中位数并不是arr[n/2],而是arr[(n+1)/2],具体证明过程略。

排序的时间复杂度是O(nlogn),空间复杂度是O(n)


思路4:按二进制下的每一位来统计。假设输入一共有5个数字,分别是1,2,1,1,3,那么他们用二进制表示分别是:

01,10,01,01,11,统计一下每一个二进制位上1出现的次数,发现:

在二进制下第一位,1出现了4次,二进制下第二位,1出现了2次。

这一位上1的次数出现次数没有>N/2,说明答案这一位上肯定是0,反之,答案这一位上肯定是1!

具体代码如下:

int num[33];
for (int i=1;i<=n;i++)
{
	int now=1;
	for (int k=0;k<31;k++,now=now*2) //枚举1,2,4,8,...
	if ((now&arr[i])>0) 	
		num[k]++;
	//位运算的操作,对这两个数字做与操作,
	//例如,arr[i]是1101,now是8也就是1000,那么他俩与之后的结果就是1000
	//如果,arr[i]是1101,now是2也就是0010,那么他俩与之后的结果就是0000
	//换句话说,如果二进制下对应那一位是0,与出来的结果就是0,否则则是一个大于0的数字
}
int now=1,ans=0;
for (int k=0;k<31;k++,now=now*2)
if (num[k]>n/2)
	ans+=now;
cout<<ans<<endl;
这样做,时间复杂度是O(N*32),空间复杂度却可以降低到O(33)≈O(1),因为数组的每一位不再需要保存了,直接读入一个处理一个就可以。


思路5(正解):

来看这样一个例子:

5 1 5 4 1 1 3 1 2 1 1

一共11个数字,其中1一共出现了6次。那么如何快速的找到这个6呢?我们来考虑这样一个现实生活中的例子:有一群人在打群架,他们每个人有一个编号,代表自己所处的势力,现在这一群人按照顺序依次往广场中央走,如果广场内现在有和自己不是一个势力的人,那么他就和其中一个同归于尽,问,最后哪一个势力的人会获胜?我们按照这个意思,再来看一下刚才这个例子:

1)势力5的一个人走进广场中央,现在广场中央有一个人,势力是5

2)势力1的一个人走进广场中央,他发现广场中央有一个势力是5的人,于是同归于尽,现在广场上没有人

3)势力5的一个人走进广场中央,现在广场中央有一个人,势力是5

4)势力4的一个人走进广场中央,他发现广场中央有一个势力是5的人,于是同归于尽,现在广场上没有人

5)势力1的一个人走进广场中央,现在广场有一个人,势力是1

6)势力1的一个人走进广场中央,现在广场有两个人,势力是2

……

可以发现,每一次火拼,势力最大(也就是出现次数最多的那个数字)的那个每次最多只会死亡一个。而火拼最多会进行N/2次,出现频率最高的那个数字最多只会损失N/2个,而题上告诉我们出现次数已经超过了一半,因此广场上剩下的那个团伙的编号必然是出现次数做多的那个!这样一来,代码就非常好写了。

int now=0,num=0; //now表示现在广场上面的团伙的编号,num表示当前有几个这样的人
for (int i=1;i<=n;i++)
{
	if (num==0) //如果广场上面没有人,那么就让这个人站在广场中央
	{
		num=1;
		now=arr[i];
	}
	else
	{
		if (now==arr[i]) //如果是同一伙人,加入团队
			num++;
		else			//如果不是同一伙人,同归于尽其中一个
			num--;
	}
}
cout<<now<<endl;


  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值