您的位置:首页 > 其它

位运算之只出现一次的的数字

2016-06-06 11:15 162 查看

******位运算系列之数组中只出现一次的数字******


//题目(1):在一个数组中只有一个数字出现一次,其他数字都是成对出现的!让你找出这个只出现一次的数字,

//其实,这也叫缺失的数字,用来找成对数组中里有一对数字缺失了一个数字的数组成员!



/*
那么,现在先来分析一下,一般情况下,我们最先想到的方法就是统计一下数组中每个成员出现的次数,然后

找出那个只出现了一次的数字,不可否认的是方法是可行的,可是效率问题就出来了,这种方法的时间复杂度

是O(n^2);看来并不是解决问题的最佳方案,还有同学会提出可以先将数组排序,然后比较相邻两个数是否相同

,可以找出这个单独出现一次的数,可是考虑一下排序的时间复杂度,显然也不是最优方案,这两种解决方法

大家可以自己试一试,才能感觉到不同!

那么,想想我们知道的位操作符,想想用二进制是否可以解决这个问题;

1.) 先回忆一下异或运算符的作用,(^) 处理二进制中两个数的异或时,相同为0,相异为1,异或还有交换的作用;

那么我们假设数组是{2,3,2,4,4},数组中成对的成员不一定是在一起的,我们先把他们的二进制列出来:

@我就不把32位都列出来了!

2:0010

3:0011

2:0010

4:0100

4:0100

既然相同位异或为0,那么让数组中的全部元素异或后的结果不就是那个单独出现一次的数字了;

0010^0011 = 0001,0001^0010 = 0011(注意,这块是不是又变回3了),0011^0100 = 0111,0111^0100 = 0011(最后的结果还是3)

那么方法是有了,开始实现代码吧!

*/


//假设数组是 arr[7] = {2,3,4,2,4};

#include<stdio.h>
#include<stdlib.h>

int FindOnce_Number(int arr[], int sz)
{
int i = 0;
int num = 0;

for(i = 0; i < sz; i++)
{
num ^= arr[i];
}
return num;
}

int main()
{
int arr[] = {2,3,4,2,4};
int sz = sizeof(arr)/sizeof(arr[0]);
int num = FindOnce_Number(arr,sz);

printf("单独出现一次的数字为:> %d\n",num);
system("pause");
return 0;
}


@ 结果:



@如果这道题不过瘾的话,那么就继续下面这道题!


题目(2): 一个数组中,只有一个数字出现了一次,其他数字都出现了三次,找出这个出现了一次的数字;


/*
分析: 这是前一道题的一个变种,我第一次做这道题时也是懵逼的,我的方法是用哈希表来求解,类似于

求字符串中的第一个只出现一次的字符,哈希表是最佳解法,但是对于这道题来说,哈希表就显得无力了,比如

让你找的数字是100000,难道你要开辟100000个int的空间吗,多么的奢侈与浪费啊,不清楚哈希表的同学,可以

查查资料,那么,这道题可以和前面的解法一样吗?将整个数组异或一遍?  显然是不可行的,因为其他成员都是

出现了三次,异或也消除不了啊,不信你可以试试!  那可真是头痛,  静下心心来分析分析:

@以{2,3,2,4,2,4,4}为例

2:0010

3:0011

2:0010

4:0100

2:0010

4:0100

4:0100

这样列出来的话,可能半天看不出来有什么特点,那么这样列出来呢:

2:0010

2:0010

2:0010

4:0100

4:0100

4:0100

3:0011

这样列出来的话是不是不要太明显,如果没有3的话,出现三次的数的二进制上的1就能被3整除,

那么,加入了3之后是不是破坏了原有的平衡,只要找出每一位1的出现不能被3整出的位,那单独

出现一次的数的这一位肯定是1,其他位如果1只出现了一次,那肯定是单独数上的1;那么,问题

是不是变得多了! 以后再有找只有一个出现一次的数,其他数都是>=3 次出现,都是这样求解! 

下面我们用代码来实现!

*/

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//难点在于找出所有数的所有位的1的个数,用较为简单的方法,以下方法为我学习大神的方法!特别棒!
int FindOnce_Number(int arr[], int sz)
{
int bit[32];
int i = 0;
int j = 0;
int num = 0;
memset(bit,0,32*sizeof(int));
//内存操作函数,头文件<string.h>,作用是将数组bit全部置0;
for(i =  0; i < sz; i++)
for(j = 0; j < 32; j++)
{
bit[j] += ((arr[i]>>j)&1);
}
//接下来就是确定单独出现数
for(j = 0; j < 32; j++)
{
if(bit[j]%3 != 0)
{
num |= (1<<j);
}
}

return num;
}

int main()
{
int arr[] = {2,3,2,4,2,4,4};
int sz = sizeof(arr)/sizeof(arr[0]);

int num = FindOnce_Number(arr,sz);
printf("出现一次的数是:> %d\n",num);

system("pause");
return 0;
}


@结果:



@既然已经讲了这么多,那就再扩展一道题祝个兴,完美结尾!

题目(3)<百度面试题>:

在一个数组中,其他元素都是成对出现,只有两个数字只出现了一次,找出这两个数;


/*
分析:先举出一个数组:{2,3,4,1,2,4};

先试试前面两道题的解法,会发现,直接异或整个数组,一下是解决不了的,那统计二进制1的个数呢?

又不行,因为有两个单独出现的数! 是不是觉得很有挑战性啊! 再仔细想想,既然和第一道题那么类似

解法会不会类似呢?先把二进制列出来:

2:0010

3:0011  

4:0100 

1:0001 

2:0010 

4:0100 

整个数组的异或结果:0010

看着二进制,是不是在想如果可以将这两个单独的数分开来该多好!就可以用第一种方法求解了。 那么想了

就试试,第一步肯定是将那两个单独的数分开存到两个数组中,这是本题的难点!!!

到这就不得不提醒你再回忆一下异或了,如果我们将整个数组异或的话,最后的结果是单独出项的那两个数异或的

最终结果!

最终异或结果(过程就不列了):0010    那么第二位是1,则说明两个单独出现的数的第二位不同;

那么我们可以根据这个条件对数组进行分组;第二位为1的为一组,第二位为0的为一组;然后每一组进行异或,

得到的两个结果就是单独出现的俩个数!(注意:我举得例子比较简单,如果最终异或结果有多个1,我们只根据

第一个出现的1的位进行分组);

代码实现如下:

*/

#include<stdio.h>
#include<stdlib.h>

void FindOnce_Number(int arr[], int sz)
{
int i = 0;
int j = 0;
int num1 = 0;
int num2 = 0;

int tmp = 0;

for(i = 0; i < sz; i++)
tmp ^= arr[i];//整个数组的异或结果既是两个单独数异或结果;
for(i = 0; i < 32; i++)
{
if(((tmp>>i)&1) == 1)
break;//找到第一个为1的位,并记住;
}
for(j = 0; j < sz; j++)
{
if(((arr[j]>>i)&1) == 0)//第i位为0的为一组全部异或;
num1 ^= arr[j];
else
num2 ^= arr[j];//剩下的则是第i位为1的,为一组全部异或;
}
//最后的结果就是num1 和 num2;
printf("单独出现的两个数分别是:%d 和 %d\n",num1,num2);
}

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

FindOnce_Number(arr,sz);

system("pause");
return 0;
}


@结果:



还可以吧! 这一篇结束!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息