C语言习题(1)

一、题目

一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。
编写一个函数找出这两个只出现一次的数字。
例如:
有数组的元素是:1,2,3,4,5,1,2,3,4,6
只有5和6只出现1次,要找出5和6.

二、思路

1、异或(^)的性质:

#进行异或的两个二进制位:相同为0,相异为1

(1)两个相同的数字进行异或结果为0,即a^a = 0,如:3^3 → 011^011 = 000 = 0

(2)任何非0数与0异或,结果还是非0数本身,即a^0 = a,如:3^0 → 011^000 = 011 = 3

(3)异或支持交换律,即a^b^a = a^a^b =(再利用性质(1)(2)化为) 0^b = b,验证:如:3^5^3 → 011^101^011 =(正常顺序计算)110^011 = 101 = 5 

2、根据以上异或的三条性质可知,一个数组中如果只有两个数字各出现一次,那么用0将数组中的所有元素累计地异或一遍,得到的结果即为两个只出现一次数字异或的结果,如:arr[10] = {1,2,3,4,5,1,2,3,4,6},用0累计异或数组中所有元素得到 0^1^2^3^4^5^1^2^3^4^6,根据性质(3)的交换律可将该式变换为0^1^1^2^2^3^3^4^4^5^6,

根据性质(1)可化简为0^0^0^0^0^5^6 → 0^5^6

根据性质(2)得0^5^6 →5^6(得到两个只出现一次数字异或后的结果)

3、用0累计异或数组中所有元素得到的5^6 = 101^110 = 011,由此可知这两个数字第1、2位的二进制位一定是不同的,我们可以只看两个数第1个不同的二进制位,即第1位,我们可以通过a&1看结果是否为1将两个数字区分开(&按位与:进行按位与的两个二进制位都为1时才为1,其余情况均为0)即5&1 = 101&001 = 001 = 1,6&1 = 110&001 = 000 = 0,如此根据按位与1后的结果将两个数字区分开。根据这个方法我们将这个数组所有的元素全部按位与1,那么这个数组就被分成各含一个只出现一次数字的两个小组,同时该数组中出现两次的元素也会被分配到同一小组中,如

按位与1结果为1的组:001(1)011(3)101(5)001(1)011(3)

按位与1结果为0的组:010(2)100(4)010(2)100(4)110(6)

 4、根据上述方法,我们就可以利用异或的性质(1)(2)(3)分别找到两个小组中只出现一次的数字

三、代码实现 

#include<stdio.h>

void Find(int arr[], int sz)
{
	int num1 = 0;
	int num2 = 0;
	int ret = 0;
	int n = 0;
	int i = 0;
	for (i = 0; i < sz; i++)//得到两个只出现一次的两个数字异或后的结果
	{
		ret = ret ^ arr[i];
	}
	for (n = 0; n < 32; n++)
	{
		if (((ret >> n) & 1) == 1)//该循环的目的是找到ret(两个数异或后的结果)二进制序列中第一个值为1的二进制位,
			break;				  //以便于后续按位与1时能够区分两个数
	}
	for (i = 0; i < sz; i++)
	{
		if (((arr[i] >> n) & 1) == 1)//数组中的元素全部按位与1从而使其分为两个小组
			num1 = num1 ^ arr[i];//1^3^5^1^3 = 5
		else
			num2 = num2 ^ arr[i];//2^4^2^4^6 = 6
	}
	printf(" num1=%d\n num2=%d\n", num1, num2);
}


int main()
{
	int arr[] = { 1, 2, 3, 4,5 , 1, 2, 3, 4, 6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Find(arr, sz);
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值