题目:一个数组里,除了三个数是唯一出现的,其余的都出现偶数个,找出这三个数中。
比如数组元素为[1,1,2,2,3,3,4,5,6],只有4,5,6这三个数字是唯一出现的,我们只需要输出4,5,6中的一个就行。
思路:
1.这个数组元素个数一定为奇数
假如有103个数,则有50对数,加上三个不同的数,则这三个和50对中的任何一个都不同。
2.因为3个数不相同, 三个数一定不可能每一bit位都相同。 如果每个bit位都相同,那么十进制的值也一定相等的。
我们可以找到其中一个数的bit位与其他两个不同,可以把那一个数从三个数字中分出来.
3.三个数肯定可以分到两组不同的数组里面去,基于这样的思路就可以找出这三个不同数字中的一个.
因为一位bit位只能区分两种状态,所以根据其中一个数的bit位与其他两个不同,可以把三个数中的二个分到一组,一个分到另一组。
下面用到异或的性质
1.A^A = 0 两个相同的数异或一定等于0
2. A^0 = A 任何数和0异或都等于0本身
比如数组元素为[1,1,2,2,3,3,4,5,6] 2的二进制是 010 ,3的二进制是011 ,如果按最后一位分,
最后一位是0在第一组,最后一位是1在第二组,那么两个2 一定都在第一组,两个3 一定都在另二组,然后
2^2 与3^3 的异或结果就都等于0. 0在与其他的单个数异或,结果就等于那个数了。
将找出的数和所有数异或,相当于又放入数组一个这个数, 这时候数组中就只有两个不同的数了。
考虑“只有两个数出现一次”的情况:可以找到一种方法,将数组划分为两部分,且让这两个数分别在不同部分,
这样每部分所有数的异或值,恰好分别等于这两个数。一种简单的分法就是,先计算出这两个数的异或值M(等价于求数组中所有数的异或值),
求出M值的二进制表示中的最低位1(其它位的1也可以,只不过麻烦点)在 第k位,然后根据第k位是否为1,将原数组分为两部分。
如4,5,6的二进制分别为0100,0101,0110。因为5的的倒数第一位为1,而其他的为0,程序第一个输出的就是5了。
程序如下:
#include<stdio.h>
#include<stdlib.h>
#define bit(t,i) ( (t) & (1 << (i) ))
#define N 9
int findOneDif(int arr[], int len)
{
int groupA, groupB, cntA, cntB, i, j;
for (i = 0; i <= 31; i++)
{
groupA = groupB = cntA = cntB = 0; //全部初始化为0
for (j = 0; j < len; j++)
{
if ( bit(arr[j], i)) //按倒数第i位分组,第i位为1在第一组,第i位为0在倒数第二组
{
groupA ^= arr[j];
cntA++;
}
else
{
//groupB ^= arr[j];
cntB++;
}
}
if (cntA % 2 == 1 && groupB != 0)
{
//A组是奇数个元素,另外两个不同的数在B组(B组的异或值就不等于零)。
printf("%d\n", groupA);
return groupA;
}
if (cntB % 2 == 1 && groupA != 0)
{
//B组是奇数个元素,另外两个不同的数在A组(A组的异或值就不等于零)。
printf("%d\n", groupB);
return groupB;
}
}
}
int findLastBit1(int n)
{
int i;
for (i = 0; i < 32; i++)
{
if (n & (1 << i))
{
return i;
}
}
}
int main()
{
int arr[] = { 1,1,2,2,3,3,4,5,6 };
int bit_pos,difNum, i,a_b, groupA, groupB, dist;
difNum = findOneDif(arr, N); //找出一个出现一次的数,一会将找出的这个数和所有的数异或
a_b = 0;
for (i = 0; i < N; i++) //(相当于找出的这个数在数组中出现2次)结果就等于另外两个不同的数异或,
{ //因为除了三个不同的数,其他的数异或结果本来就为零。
a_b ^= arr[i];
}
a_b ^= difNum; //求出两个数的异或值
bit_pos = findLastBit1(a_b); //找出异或结果最后一位不为1的位置,假设为k位
dist = (1 << bit_pos);
groupA = groupB = 0;
for (i = 0; i < N; i++)
{
if (arr[i] == difNum) //将数组中找的那一个不同的数排除掉
continue;
if (arr[i] & dist) //k位为1,分第一组
{
groupA ^= arr[i];
}
else
{ //k位为0,分第二组
groupB ^= arr[i];
}
}
printf("%d %d %d\n", groupA, groupB, difNum);<span style="white-space:pre"> </span>//分出了三个数
system("pause");
}