一个数组中有三个数字a、b、c只出现一次,其他数字都出现了两次。请找出三个只出现一次的数字。

在一组数组中,除三个数字各出现一次,其他数字都出现偶数次。通过位运算策略,可以找到这3个数字中的任意一个。首先,按某位异或分组,奇数个数的数组异或结果不为0,表示包含一个不重复的数字;然后,找到一个不重复的元素,以此为基础再次分组,确定其余两个不重复的数字。

本文参考:http://zhedahht.blog.163.com/blog/static/25411174201283084246412/

http://blog.csdn.net/wypblog/article/details/8032898

题目:一个数组里,除了三个数是唯一出现的,其余的都出现偶数个,找出这三个数中的任一个。

比如数组元素为【1, 2,4,5,6,4,2】,只有1,5,6这三个数字是唯一出现的,我们只需要输出1,5,6中的一个就行。

解题思路:

在这道题中,如果我们能够找出一个只出现一次的数字,剩下两个只出现一次的数字就很容易找出来了。这个数组元素个数一定为奇数,而且那要求的三个数一定不可能每一bit位都相同,所以我们可以根据第一个不同的bit位,可以把那三个数字分出来,而且可以推出三个数肯定可以分到两组不同的数组里面去,基于这样的思路就可以找出这三个不同的数字。

下面是找到数组中一个不同的数字:

//返回数组中一个不重复的数
int FindSingleNumber(int arr[],int  len)
{
	invaluedInput = false;
	if(NULL == arr || len<3)
	{
		invaluedInput = true;
		return 0;
	}
	int tmpA = 0;
	int tmpB = 0;
	int countA = 0;
	int countB = 0;
	int i = 0;
	int j = 0;
	for(i=0; i<32; i++)
	{
		tmpA = tmpB = countA = countB = 0;
		//根据第i位的不同,将数组中的元素分为2部分,tmpA、tmpB分别异或两部分元素
		for(j=0; j<len; j++)
		{
			if(IsNumOf1(arr[j],i))
			{
				tmpA ^= arr[j];
				countA++;
			}
			else
			{
				tmpB ^= arr[j];
				countB++;
			}
		}
		//如果countA为奇数,那么tmpA异或的那部分元素或者包含三个不重复元素中的一个,或者包含全部三个不重复的元素
		if(countA & 0x1)
		{
			if(0 == tmpB)//如果tmpB为0,那么可得tmpA异或的那部分数据中包含全部的三个不重复元素,所有三个不重复元素的当前的第i位都相同,不足以区分。
				continue;
			else
			{
				//printf("%d\n",tmpA);
				return tmpA;//因为tmpB不为0,countA为奇数,那么countB就为偶数,并且tmpB异或的那部分数据中包含2个不重复元素,此时tmpA异或的那部分数据就只包含一个不重复的数据。
			}
		}
		else //否则countA为偶数,那么countB为奇数
		{
			if(0 == tmpA)//同理:如果tmpA为0,那么可得tmpB异或的那部分数据中包含全部的三个不重复元素,所有三个不重复元素的当前的第i位都相同,不足以区分。
				continue;
			else
			{
			//	printf("%d\n",tmpB);
				return tmpB;//否则tmpA那部分数据集合中包含2个不重复的元素,tmpB的那部分数据集合中包含一个不重复的元素,异或结果中相同的数抵消,结果就是tmpB,返回
			}
		}
	}
}
总结下上面的思路就是:

1、原数组的个数一定是奇数

2、按照某一位是1还是0分为两个数组:分成两个数组一个是奇数个元素,一个是偶数个元素 ,相同的数字一定会被分到同一组里, 分出来的两个数组各自异或的结果,奇数个数的数组肯定不为0(包含1个或3个不重复的元素)。偶数个数的数组可能为0(不包含不重复的元素),可能不为0(包含2个不重复的元素)。偶数个数的数组异或结果为0,表示三个不重复的数都被分到同一组中了,包含此部分数的数组有奇数个元素。另一偶数部分的数组中不包含不重复的元素。偶数个数的数组异或结果不为0,表示偶数个数的数组里包含2个出现一次的数,那么奇数个数的数组中包含一个不重复的数,由于相同元素异或为0,相抵消,奇数个数的数组所有数的异或结果就为那个不重复的数。

3、找到一个不重复的元素后就可以很方便的找到剩下的两个不重复的元素。

代码如下:

//找到两个只出现一次的数,通过引用tmpA,tmpB返回。,singleNum为FindSingleNumber函数返回的数组中的一个不重复的元素。
void FindOnceNum(int arr[],int len,int& tmpA,int& tmpB,int singleNum)
{
	invaluedInput = false;
	if(NULL == arr || len<2)
	{
		invaluedInput = true;
		return;
	}
	int i = 0;
	int result = 0;
	int tmp = 0;
	tmpA = 0;
	tmpB = 0;
	for(i=0; i<len; i++)
	{
		result ^= arr[i];
	}
	/*result与所有数组元素都异或过后,还应该与singleNum异或来抵消FindSingleNumber函数返回的数组中的那个不重复的元素,这样数组就只剩下2个不重复的元素*/
	result ^= singleNum;
	//找到第一个不为0的位
	tmp = TheFirst1OfNum(result);
	//result为0则数组中不存在不重复的元素,输入有误,返回。
	if(tmp == 0)
	{
		invaluedInput = true;
		return;
	}
	//根据result的第一个不为1的位将数组元素分为两部分。
	for(i=0; i<len; i++)
	{
		if(arr[i] & tmp)
			tmpA ^= arr[i];
		else
			tmpB ^= arr[i];
	}
	//记得还要将singleNum加进来,才能保证数组中只有2个不同的元素。
	if(singleNum & tmp)
		tmpA ^= singleNum;
	else
		tmpB ^= singleNum;
}
上面函数完成寻找数组中只有两个不重复元素的功能,通过引用返回。

寻找数组中只要两个不重复元素的方法为:任何数字异或它自己结果都为0.也就是说如果数组中只有1个不重复的元素,那么从头到尾依次异或,最终结果刚好是那个只出现一次的数字,那些成对出现两次的数字全部在异或中抵消了。

那么对于寻找数组中两个不重复的元素:我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现依次的数字的异或结果。因为其他数字都出现了两次,在异或中全部抵消了。由于这两个数字肯定不一样,那么异或结果肯定不为0,也就是说在这个结果数字的二进制表示中至少就有一位为1,我们在结果数字钟找到第一个为1的位的位置,记为n位。现在我们一第n为是不是1的位标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第n位都是1,而第二个子数组中每个数字的第n位都是0.由于我们分组的标准时数字中的某一位是1还是0,那么出现了两次的数字肯定被分配到同意个子数组。因为两个相同的数字的任意一位都是相同的,我们不可能把两个相同的数字分配到两个子数组中区,于是我们已经把原数组分成两个子数组,每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。我们已经知道如何在数组中找出唯一一个只出现一次数组,因此到此为止所有的问题都已经解决了。

举个例子,假设输入数组{2,4,3,6,3,2,5,5}。当我们依次对数组中的每一个数字做异或运算之后,得到结果用二进制表示是0010.异或得到结果中的倒数第二位是1,于是我们根据数字的倒数第二位是不是1分为两个数组。第一个子数组{2,3,6,3,2}中所有数字的倒数第二位都是1,而第二个子数组{4,5,5}中所有数字的倒数第二位都是0.接下来只要分别对这两个子数组求异或,就能找出第一个子数组中只出现依次的数字是6,而第二个子数组中只出现一次的数字是4.

解决本题的完整代码如下:

#include <iostream>
#include <stdio.h>
using namespace std;

//错误标记
bool invaluedInput = false;

//检查num的第n位是否为1
bool IsNumOf1(int num,int n)
{
	if(num & (1<<n))
		return true;
	else
		return false;
}

//返回数组中一个不重复的数
int FindSingleNumber(int arr[],int  len)
{
	invaluedInput = false;
	if(NULL == arr || len<3)
	{
		invaluedInput = true;
		return 0;
	}
	int tmpA = 0;
	int tmpB = 0;
	int countA = 0;
	int countB = 0;
	int i = 0;
	int j = 0;
	for(i=0; i<32; i++)
	{
		tmpA = tmpB = countA = countB = 0;
		//根据第i位的不同,将数组中的元素分为2部分,tmpA、tmpB分别异或两部分元素
		for(j=0; j<len; j++)
		{
			if(IsNumOf1(arr[j],i))
			{
				tmpA ^= arr[j];
				countA++;
			}
			else
			{
				tmpB ^= arr[j];
				countB++;
			}
		}
		//如果countA为奇数,那么tmpA异或的那部分元素或者包含三个不重复元素中的一个,或者包含全部三个不重复的元素
		if(countA & 0x1)
		{
			if(0 == tmpB)//如果tmpB为0,那么可得tmpA异或的那部分数据中包含全部的三个不重复元素,所有三个不重复元素的当前的第i位都相同,不足以区分。
				continue;
			else
			{
				//printf("%d\n",tmpA);
				return tmpA;//因为tmpB不为0,countA为奇数,那么countB就为偶数,并且tmpB异或的那部分数据中包含2个不重复元素,此时tmpA异或的那部分数据就只包含一个不重复的数据。
			}
		}
		else //否则countA为偶数,那么countB为奇数
		{
			if(0 == tmpA)//同理:如果tmpA为0,那么可得tmpB异或的那部分数据中包含全部的三个不重复元素,所有三个不重复元素的当前的第i位都相同,不足以区分。
				continue;
			else
			{
			//	printf("%d\n",tmpB);
				return tmpB;//否则tmpA那部分数据集合中包含2个不重复的元素,tmpB的那部分数据集合中包含一个不重复的元素,异或结果中相同的数抵消,结果就是tmpB,返回
			}
		}
	}
}

//返回num从低位开始第一位不为0所对应1左移的结果,如num=10110,则返回00010,如num=110000则返回010000,这里都是用2进制表示
int TheFirst1OfNum(int num)
{
	if(num == 0)
		return 0;
	int one = 1;
	int i = 0;
	for(i=0; i<32; i++)
	{
		if(num & one)
			return one;
		else
			one = one<<1;
	}
}

//找到两个只出现一次的数,通过引用tmpA,tmpB返回。,singleNum为FindSingleNumber函数返回的数组中的一个不重复的元素。
void FindOnceNum(int arr[],int len,int& tmpA,int& tmpB,int singleNum)
{
	invaluedInput = false;
	if(NULL == arr || len<2)
	{
		invaluedInput = true;
		return;
	}
	int i = 0;
	int result = 0;
	int tmp = 0;
	tmpA = 0;
	tmpB = 0;
	for(i=0; i<len; i++)
	{
		result ^= arr[i];
	}
	/*result与所有数组元素都异或过后,还应该与singleNum异或来抵消FindSingleNumber函数返回的数组中的那个不重复的元素,这样数组就只剩下2个不重复的元素*/
	result ^= singleNum;
	//找到第一个不为0的位
	tmp = TheFirst1OfNum(result);
	//result为0则数组中不存在不重复的元素,输入有误,返回。
	if(tmp == 0)
	{
		invaluedInput = true;
		return;
	}
	//根据result的第一个不为1的位将数组元素分为两部分。
	for(i=0; i<len; i++)
	{
		if(arr[i] & tmp)
			tmpA ^= arr[i];
		else
			tmpB ^= arr[i];
	}
	//记得还要将singleNum加进来,才能保证数组中只有2个不同的元素。
	if(singleNum & tmp)
		tmpA ^= singleNum;
	else
		tmpB ^= singleNum;
}

int main()
{
	int arr[] = {1,2,3,4,5,5,7,2,6,7,4};
	int len = sizeof(arr)/sizeof(int);
	//找到一个不重复的元素
	int tmp = FindSingleNumber(arr,len);
	int tmpA = 0;
	int tmpB = 0;
	if(0 == tmp && invaluedInput)
	{
		cout<<"invaluedInput"<<endl;
	}
	else
	{
		FindOnceNum(arr,len,tmpA,tmpB,tmp);
		if(invaluedInput)
			cout<<"invaluedInput"<<endl;
		else
		{
			cout<<tmp<<endl<<tmpA<<endl<<tmpB<<endl;
		}
	}
	return 0;
}


另一种简单的解法:如果允许另外开辟空间并且知道数组的最大元素的话,那么可以另外建一个统计数组中元素出现次数的hash表,最后扫面这个hash表,将为1的项输出即可。





 



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值