【位运算异或】找出一个数组中独特的数

问题1:数组中有101个正数,其中有50对数是两两重复的,剩余一个数和其他所有数都不相同。求出这个独特的数。

问题2:数组中有102个正数,其中有50对数是两两重复的,剩余两个数和其他所有数都不相同。求出这两个独特的数。

问题3:数组中有103个正数,其中有50对数是两两重复的,剩余三个数和其他所有数都不相同。求出这三个独特的数。

 

这题考查了异或的两个性质,a^a=0,a^0=a

先处理最简单的题1

101个数里有五十对两两相同,假设独特的数是a,所以将这101个数全部异或起来,得到的异或和是0^0^0^……^0^a=a。所以很明显,只要把数组内所有元素异或在一起,结果就是那个独特的数。

#include<stdio.h>
//数组中有101个正数,其中有50对数是两两重复的,剩余一个数和其他所有数都不相同。求出这个独特的数。
void find1num(int arr[], int size, int res[]) {//寻找1个单独数
	int m = 0;
	for (int i = 0; i < size; i++) {
		m ^= arr[i];
	}
	res[0] = m;
}

int main() {
	int arr1[] = { 24,40,29,54,59,45,30,34,53,52,55,25,49,16,37,32,57,15,13,36,36,57,20,15,34,43,39,10,31,14,45,19,21,11,58,54,50,35,25,16,48,20,19,31,40,50,41,22,47,23,43,41,52,17,53,38,32,27,59,10,58,48,56,27,46,24,42,23,28,38,12,26,55,22,33,17,30,12,14,37,26,39,44,35,56,1,29,49,18,44,28,18,51,21,46,13,33,11,47,42,51 };
	int res[10];
		
	find1num(arr1, 101, res);
	printf("独特的数为%d\n", res[0]);

	system("pause");
	return 0;
}


再来看题2

依照题1的思路,假设独特的数是a和b,将数组里的102个数全部做异或,得到的结果是0^0^0^……^0^a^b=a^b,记作res,这个res肯定不可能为0(不然a和b不就一样了嘛)。

然后将这个res与他的相反数-res做一个按位与的操作(res&-res的操作是用来从最低位查找res的机器数中第一个不为零的位)。由这个运算结果可以得到,res中从低到高第一个不为0的位,打个比方,是第17位。所以a和b在第17位上必然一个是1,一个是0(别忘了res是a和b按位异或的结果)。

现在我们再回头考虑那五十对两两相同的数,这些数中,有一些第17位是1,有一些第17位是0。所以我们不妨把这五十对数分成两部分,一部分第17位和a相同,归为一类;剩下的第17位和b相同,归为另一类。现在,每一堆里面都是n对两两相同的数和一个独特的数,我们分别把两堆里独特的数找出来,就是a和b了,这就又转化成了题1。

#include<stdio.h>
//数组中有102个正数,其中有50对数是两两重复的,剩余两个数和其他所有数都不相同。求出这两个独特的数。
void find1num(int arr[], int size, int res[]) {//寻找1个单独数
	int m = 0;
	for (int i = 0; i < size; i++) {
		m ^= arr[i];
	}
	res[0] = m;
}
void find2num(int arr[], int size, int res[]) {//寻找2个单独数
	int m = 0;
	for (int i = 0; i < size; i++) {
		m ^= arr[i];
	}
	int bitvalue = m & -m; //求divider
	int fir = 0, sec = 0; //分堆
	for (int i = 0; i < size; i++) {
		if ((arr[i] & bitvalue) == 0) fir ^= arr[i];
		else sec ^= arr[i];
	}
	res[0] = fir;
	res[1] = sec;
}

int main() {
	int arr1[] = { 24,40,29,54,59,45,30,34,53,52,55,25,49,16,37,32,57,15,13,36,36,57,20,15,34,43,39,10,31,14,45,19,21,11,58,54,50,35,25,16,48,20,19,31,40,50,41,22,47,23,43,41,52,17,53,38,32,27,59,10,58,48,56,27,46,24,42,23,28,38,12,26,55,22,33,17,30,12,14,37,26,39,44,35,56,1,29,49,18,44,28,18,51,21,46,13,33,11,47,42,51 };
	int arr2[] = { 39,33,14,32,28,43,24,12,46,49,51,38,25,49,39,58,17,37,11,58,20,35,16,13,35,23,44,15,37,47,56,36,10,19,16,53,50,50,29,59,21,42,36,22,26,29,41,48,57,46,40,11,27,31,22,55,47,56,34,20,18,12,48,32,13,18,23,54,40,53,54,51,14,55,1,34,24,59,41,17,25,43,38,26,10,33,52,2,44,19,57,27,21,45,52,15,42,28,30,45,30,31 };
	int res[10];

	find2num(arr2, 102, res);
	printf("独特的数为%d %d\n", res[0], res[1]);

	system("pause");
	return 0;
}

再来看题3

直接套用题2的思路是不好求的,因为三个分别不为0的数异或的结果有可能是0(011^101^110=000)。但是分堆的思想一定是正确的。假设我们能将这103个数分为两堆,其中一堆是a和n个两两相同的数,另一堆是b和c和n个两两相同的数,问题不就转化成了一个题1和一个题2吗?

那么我们怎么分堆呢?题2分堆是采用了res&-res的结果,但这次不能这么用了,原因前两行讲了,res可能为0。那么怎么办?也无所谓,反正我们是按照机器数的某一位的值进行分堆的,直接把32位(如果是整形的话)全遍历一遍就行了。

好的,假设我们遍历到某个可能是正确的位上,跟据该位上的1和0分成了AB两堆,现在就发现了更重要的问题,我们怎么知道我们分对没有呢?题2我们不担心这个是因为题2的a和b在这一位上一定不同,现在可不一定了,万一把三个独特的数全分在同一堆里那不是瞎忙活嘛。

我们再想想,103个数分成两堆,一定一堆是奇数个元素,另一堆是偶数个元素。如果我们分成功了,也就是一堆里是a+2n的组合,另一堆是b+c+2m的组合,说明偶数个元素的堆全部异或的结果一定是不为0的;那如果我们分失败了,也就是一堆是2n的组合,另一堆是a+b+c+2m的组合,说明偶数个元素的堆全部异或的结果一定是为0的。所以我们可以根据分完堆后偶数个元素的堆全部异或的结果来判断有没有分堆成功,只要异或和不为0,就成功,成功之后,题3就转化成了一个题1+一个题2;如果失败,遍历下一个位。

#include<stdio.h>
//数组中有103个正数,其中有50对数是两两重复的,剩余三个数和其他所有数都不相同。求出这三个独特的数。
void find1num(int arr[], int size, int res[]) {//寻找1个单独数
	int m = 0;
	for (int i = 0; i < size; i++) {
		m ^= arr[i];
	}
	res[0] = m;
}
void find2num(int arr[], int size, int res[]) {//寻找2个单独数
	int m = 0;
	for (int i = 0; i < size; i++) {
		m ^= arr[i];
	}
	int bitvalue = m & -m; //求divider
	int fir = 0, sec = 0; //分堆
	for (int i = 0; i < size; i++) {
		if ((arr[i] & bitvalue) == 0) fir ^= arr[i];
		else sec ^= arr[i];
	}
	res[0] = fir;
	res[1] = sec;
}
void find3num(int arr[], int size, int res[]) {//寻找3个单独数
	for (int i = 1, m = 0; m < 32; i <= 1, m++) {
		int fir = 0, fir_num = 0, sec = 0, sec_num = 0, arr1[103], arr2[103];
		for (int j = 0; j < size; j++) {
			if ((arr[j] & i) == 0) {
				fir ^= arr[j];
				arr1[fir_num++] = arr[j];
			}
			else {
				sec ^= arr[j];
				arr2[sec_num++] = arr[j];
			}
		}
		if (fir != 0 && fir_num % 2 == 0) {
			res[2] = sec;
			int bitvalue = fir & -fir;
			int a = 0, b = 0; 
			for (int i = 0; i < fir_num; i++) {
				if ((arr1[i] & bitvalue) == 0) a ^= arr1[i];
				else b ^= arr2[i];
			}
			res[0] = a;
			res[1] = b;
			break;

		}
		if (sec != 0 && sec_num % 2 == 0) {
			res[2] = fir;
			int bitvalue = sec & -sec;
			int a = 0, b = 0; 
			for (int i = 0; i < sec_num; i++) {
				if ((arr[i] & bitvalue) == 0) a ^= arr1[i];
				else b ^= arr2[i];
			}
			res[0] = a;
			res[1] = b;
			break;
		}
	}
}
int main() {
	int arr3[] = { 42,14,43,6,25,46,12,59,5,52,55,14,26,31,17,41,56,23,11,18,24,57,21,15,34,33,28,13,10,28,31,49,32,48,19,43,33,10,42,54,22,21,36,24,58,35,57,26,40,37,51,35,16,58,38,13,50,39,29,30,45,48,17,27,52,47,25,54,29,36,15,19,3,11,51,49,44,56,59,23,50,16,32,30,53,47,53,22,34,41,20,27,55,39,12,44,45,18,40,38,20,46,37 };
	int res[10];
	
	find3num(arr3, 103, res);
	printf("独特的数为%d %d %d\n", res[0], res[1], res[2]);
    
	system("pause");
	return 0;
}

 

这个问题用计数排序,哈希表统计每个元素出现的次数很好做,待笔者有空回来补这段代码.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值