找出数组中一个(两个,三个)只出现一次的数字

面试时候碰到这么一道题,给定一个数组,里面的数字都是两两出现,但是假如现在里面有一个(两个,三个)只出现一次的数字,请问你怎么找出来他们。

1. 第一种情况,找出里面一个只出现一次的数字,这个比较简单,因为里面的数字都两两出现,我们可以考虑使用异或(^),0与任何数异或都得到那个数本身,同时两个相同的数异或结果为0,这样将所有数字进行异或,可以发现结果为那个只出现一次的数字。

*void FindOneDiffer(int* num,int N)
{
	int i;
	int notSame=0;
	for(i=0;i<N;i++)
	{
		notSame^=num[i];
	}
	if(notSame)//判断输入中是否只有一个不相同的
	    cout<<notSame<<endl;
}

2. 第二种情况,找出里面两个只出现一次的数字,说明其他的数字都是两两出现,只有数字A,B是只出现了一次,(A!=B),这时候考虑先将所有数字都异或,得到X=A^B,此时X不为0,因为A!=B,那么我们可知对于X的二进制数必定有一位为1,此时我们找出X的从右向左数第一个不为0的位数,即为m,那么我们可知A和B中必有一个第m位为1,另一个为0,这是因为如果两个第m位都为0或者都为1,那么必然会使得X的第m位为0,因此我们可以根据这个第m位是否为1,将整个数组划分为两部分,一组是第m位为1,另一组是第m位为0,然后分别对两组里面的数字进行异或,可得这两个数字。

void FindTwoDiffer(int* num,int N)
{
	int i,j,s1=0,s2=0;
	int tmp=0;
	for(i=0;i<N;i++)
	{
		tmp^=num[i];
	}
	i=1;
	while(!(i&tmp))
	{
		i<<=1;
	}

	for(j=0;j<N;j++)
	{
		if(i&num[j])
			s1^=num[j];
		else
			s2^=num[j];
	}
	cout<<s1<<','<<s2<<endl;
}

3. 第三种情况是找出里面三个只出现一次的数字,说明其他的数字都是两两出现,只有数字A,B,C是只出现了一次,我们再次要考虑的仍是如何将这三个数字分成两组,第一组有一种特征,第二组有另一种特征,这样就可以将这三个数字分成两组,在接下来对里面有两个只出现一次的数字的数组使用第二种情况的办法就可以将里面两个数字分开,这样就可以分开这三个数字,那么现在的关键就是要找到能将三个数字分成两组的特征。

在此,我们首先考虑2中的方法,X=A^B^C,但是在这里X既可为0(A=2,B=5,C=7),也可以不为0,因此,我们无法根据X的第m位是否为1将其划分,因此,考虑另一种办法,将X再与其他所有数字异或,可得a=X^A=B^C,b=X^B=A^C,c=X^C=A^B,X^D,X^D,X^E,X^E...,此时可以发现x=a^b^c=(B^C)^(A^C)^(A^B)=0,

由于a,b,c各不为0,并且互不相等。(由于A,B,C各不相等,所以a,b,c各不为0)(假设a,b相等,那么0=a^b=(B^C)^(A^C))=A^B,由于A与B不相等,那么可知此式不相等,假设不成立,所以a,b,c互不相等) 。

我们定义一个函数,函数f(n)表示n的二进制表示时,从右往左数第一个位为1,其他所有位为0的数,比如f(4)=f(0x100)=4;f(n)=n&(-n);因此,f(a),f(b),f(c)都不为0,因为a,b,c都不为0,此时考虑f(a)^f(b)^f(c),由于f(a)和f(b)中都只有一位为1,那么f(a)^f(b)的结果要么都为0,要么有两个1,而f(c)的结果中只有一位为1,因此,f(a)^f(b)^f(c)的结果不为0,此时假设f(a)^f(b)^f(c)的二进制中至少有1位为1,设为第m位,那么我们可知a,b,c中要么有一个第m位为1,要么有三个第m位为1。

这里我们假设a=X^A,b=X^B,c=X^C的第m位都为1,那么可知A,B,C的第m位都与X的第m位相反,首先假设A,B,C的第m位都为0,那么X的第m位将为1,由于X=A^B^C,那么对于X而言,第m位由A,B,C的第m位异或而来,应该为0,但是却与假设矛盾,因此假设不成立。其次假设A,B,C的第m位都为1,那么X的第m位将为0,由于X=A^B^C,那么对于X而言,第m位由A,B,C的第m位异或而来,应该为1,但是却与假设矛盾,因此假设不成立,所以可知a,b,c中只有一个第m位为1,其他两个第m位为0,这样我们就可以将三个数首先划分为两组,然后再继续根据情况2进行划分。

int isFirstBitof1(int value)
{
	return (value&-value);
}
void FindThreeDiffer(int* num,int N)
{
	int i,x=0;
	for(i=0;i<N;i++)
		x^=num[i];

	int firstbit1=0;
	for(i=0;i<N;i++)
	{
		firstbit1^=isFirstBitof1(x^num[i]);
	}
	firstbit1=isFirstBitof1(firstbit1);

	int first=0;
	for(i=0;i<N;i++)
	{
		if(firstbit1==isFirstBitof1(x^num[i]))
		{
			first^=num[i];
		} 
	}
	printf("%d",first);
	cout<<',';
	for(i=0;i<N;i++)
	{
		if(first==num[i])
		{
			int temp;
			temp=num[N-1];
			num[N-1]=num[i];
			num[i]=temp;
		}
	}
	FindTwoDiffer(num,N-1);
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值