从面试题中学算法(2)---求数组中唯一n个出现1次的数字(n=1,2,3)

来源:互联网 发布:重庆大学网络教学平台 编辑:程序博客网 时间:2024/06/05 13:55

       题目要求:给定一个整数数组,其中只有1个数出现1次,其他数字都出现偶数次。找出这个数字。如果其中只有2个数字出现1次呢?3个数字出现一次呢?依次找出。

   例子:数组a[] ={1,2,3,4,2,1,3} 那么程序输出 仅出现一次的数字为4。数组a[] = {1,2,3,2,3,4}.那么程序输出2个仅出现一次的数字为1,4. 数组a[]= {1,2,3,3,2,4,6}那么程序输出 3个仅出现一次的数为1,4,6.

好了,题目理解清楚了,下面就考虑解法了。首先,说明一个位运算---异或。这个对解题非常重要。异或是位运算的一种,指的是两个数字的二进制,每个位逐一异或。两个位相同则该位为0,不同则为1。异嘛,肯定不同为1.比如:3 ^ 4 = (011)^(100) =(111)。并且异或满足交换律。这个交换律很重要。x=a^b^c=a^c^b。0和x异或都为x。

下面说明3个非常重要的等式。

1. -n = (~n+1) = ~(n-1)   

        一个数取负数运算将等于该数按位取反加1,并且也等于该值减1取反。从补码角度理解,第二个式子恰好为负数的补码(负数的补码为源码取反加1)对应于n而言,其负数就为-n嘛。

2.保留一个数字的最右边为1的位,其他位都置为0.   n = n &(~n+1 )

比如n为十进制 7,对应二进制为0111.那么保留该数字的最右边1为: n = n &(~n+1 )=0001。可见只保留了最右边位为1的位。

3.将一个数的最右边1变为0,其余的位不变。 n = n &(n-1) .比如n为 十进制7,对应二进制为0111.n = n &(n-1) =0110。仅将最右边为1的位变为0。

一.找出1个仅出现一次的数字

在数组中,只有一个数字出现一次,其他数字都成对出现,那么如果对整个数组进行异或操作呢?由于异或操作满足交换律,而两个相同的数字之间异或为0,而0和任意数异或都为任意数。所以遍历一遍数组,且依次将结果异或,最后的异或值就是所查找的数字。比如a[] ={1,2,3,4,2,1,3}  xorRst 为异或值。将数组中所有元素异或下。xorRst =1^2^3^4^2^1^3。根据交换律xorRst =(1^1)^(2^2)^(3^3)^4其中括号内的值都为0.所以xorRst =0^0^0^4=4.即是结果。代码如下:

int FindSingleNum(int a[],int len){int Rst = 0;if(a != NULL && len > 0){for(int i=0 ; i<len; ++i)Rst ^= a[i];}return Rst ;}

二.找出2个仅出现一次的数字

       那么问题升级为数组中有2个仅出现一次的数字。既然我们有了上面的分析,那么2个数字的能不能按照上面思路走呢?如果我们找到了区分这个2个数字的规则,就可以将该2个数字分到2组中去,而对于每个组而言,都可以按照出现1个数字的方法解决。那么问题就解决了。那现在是如何区分这个两个数字呢?我们还是将所有元素进行异或后结果。xorRst = a^b。(a,b为仅出现1次的2个数,其他成对的数都异或为0了,见前面分析)。由于a,b不同,所以a,b的对应的每个位至少有一个位不同,我们如果找出这个了位,那么a,b就可以依次分入到两个不同的组中去。而xorRst的最右边为1的数(如何求就是前面介绍的3个等式之一)。就完全表示。假设xorRst最右边为1的位是第m位。则a,b的第m位肯定不同。依次来将数组元素进行分组,相同的数字肯定是会分入到同一组中(因为相同的数每位都相同)。

int LastBitof1(int num){return num & (~num+1) ;}bool FindTwoOnceNum(int a[],int len,int &first,int &second){bool RstState = false ;if(a != NULL && len > 0){int xorRst = 0 ;for(int i= 0 ; i < len ; ++i)xorRst ^= a[i] ;xorRst = xorRst & (~xorRst +1 );//get last bit of 1first = second = 0 ;for(int j=0 ;j<len ;++j){if(a[j] & xorRst ) //通过xorRst 来分组first ^= a[j];elsesecond ^= a[j];}RstState = true ; }return RstState ;}

三.找出3个仅出现一次的数字

那么问题如果在升级,一个数组中有3个仅出现一次的数字呢?有了前面两个问题的思路,我们猜想,也就是该如何进行分组?将a,b,c三个数如果分入到2个组中,分别对两组求解异或值,其中一组的异或结果就是a,b,c中的一个,有了这个,那么问题就回到第二个问题了。也就解决了。那么关键是如何进行a,b,c的分组呢?如何找到一个却别a,b,c的方法呢?

依然继续将数组中所有的元素进行异或求值。其结果为 xorRst = a^b^c(a,b,c为三个仅出现一次的数)。

下面将开启一连串的逻辑推导。

1.由于a,b,c三个数互不相等。所以xorRst与a,b,c都不相等。

证明:假设xorRst与a相等。 则  a = a^b^c.那么 b^c=0 ,即是 b等于c,与a,b,c互不相等矛盾。

2.因为xorRst与a,b,c都不相等。所以xorRst^a,xorRst^b,xorRst^c都不为0.(两个不同数异或结果肯定不为0)

3.假设有这样一个函数f(n)=lastBifof1(n)(问题2代码中的一个函数),f(n)函数目的就是保留数n的最右边1位,其他位都置为0。这是前面提到的3个等式中的一个。所以看下:这样的结果

f(xorRst ^a ) ^f(xorRst ^b ) ^f(xorRst ^c )  。这个式子意思就是,分别求xorRst^a,xorRst^b,xorRst^c最右边为1的位。然后将结果异或起来。从二进制角度看,其实也就是 三个只有一个位为1的数进行异或。因为f(xorRst ^a ) , f(xorRst ^b ) ,f(xorRst ^c ) 三个数都不为0.所以f(xorRst ^a ) ^ f(xorRst ^b ) ^f(xorRst ^c ) 结果也不为0(如果该结果为0,那么至少有一个f(xorRst^n)(n=a,b,c)为0,与前面f(xorRst^a,b,c)不为0矛盾。

所以该条结论:A= f(xorRst ^a ) ^ f(xorRst ^b ) ^f(xorRst ^c ) 不为0. 即是至少有一位为1.

4. 假设A的第m位为1,那么得出的结论就是 xorRst ^a,xorRst^b,xorRst^c 这三个数中仅有一个数的第m位为1或者三个数的第m位都为1.(这是很好得出的 1=1^1^1或者 1=0^1^0 就这两种情况)。

下面证明:xorRst ^a,xorRst^b,xorRst^c 三个数的第m位都为1的情况是不可能的。

证明:假设xorRst^a,xorRst^b,xorRst^c 第m位都为1.那么xorRst 的第m位都与a,b,c的第m位相反(位不同才为1嘛)。那么也就是a,b,c的第m位都相同。

4.1 假设a,b,c的第m位都为0.因为xorRst与a,b,c在第m位都相反,所以 xorRst 的第m位为1.有因为xorRst=a^b^c。所以在xorRst在第m位上是为0的。与前面矛盾。

4.2 假设a,b,c的第m位都为1.因为xorRst与a,b,c在第m位都相反,所以 xorRst 的第m位为0,又因为xorRst=a^b^c 所以xorRst的第m位为1.与前面的矛盾。

5.结论:xorRst ^a,xorRst^b,xorRst^c 这三个数中仅有一个数的第m位为1。由此找到了区分a,b,c三者的方法。那么依据xorRst ^n的第m位来分组a,b,c三个数进入到两个不同组中去。

void Swap(int &a,int &b){int tmp = a;a = b;b = tmp;}bool FindTreeOnceNum(int a[],int len, int &first,int &second, int &third){bool RstState = false ;if(a != NULL && len > 0){int xorRst = 0,i;//异或结果for(i=0; i<len; ++i)xorRst ^= a[i];//下面求 f(x^a)^f(x^b)^f(x^c)的最右边为1的第m位int  flag =0;for(i=0 ; i<len ; ++i)flag ^= LastBitof1(xorRst ^ a[i]);flag = LastBitof1(flag); //只保留最右边为1的位//求firstfirst = second = third = 0 ;vector<int> vec;for(i=0 ; i<len ;++i)if(LastBitof1( xorRst ^ a[i] ) == flag )//x^a的第m位为1的情况 这里的m位其实是最右边为1位first ^= a[i];  //这样first 就求出了for(i=0 ; i<len ;++i)if( a[i] == first)Swap(a[i],a[len-1]);//移除该first值FindTwoOnceNum(a,len-1,second,third);RstState = true; }return RstState ;}

四.完整代码及其测试

#include <iostream>#include <vector>using namespace std;int FindSingleNum(int a[],int len){int Rst = 0;if(a != NULL && len > 0){for(int i=0 ; i<len; ++i)Rst ^= a[i];}return Rst ;}int LastBitof1(int num){return num & (~num+1) ;}bool FindTwoOnceNum(int a[],int len,int &first,int &second){bool RstState = false ;if(a != NULL && len > 0){int xorRst = 0 ;for(int i= 0 ; i < len ; ++i)xorRst ^= a[i] ;xorRst = xorRst & (~xorRst +1 );//get last bit of 1first = second = 0 ;for(int j=0 ;j<len ;++j){if(a[j] & xorRst ) //通过xorRst 来分组first ^= a[j];elsesecond ^= a[j];}RstState = true ; }return RstState ;}void Swap(int &a,int &b){int tmp = a;a = b;b = tmp;}bool FindTreeOnceNum(int a[],int len, int &first,int &second, int &third){bool RstState = false ;if(a != NULL && len > 0){int xorRst = 0,i;//异或结果for(i=0; i<len; ++i)xorRst ^= a[i];//下面求 f(x^a)^f(x^b)^f(x^c)的最右边为1的第m位int  flag =0;for(i=0 ; i<len ; ++i)flag ^= LastBitof1(xorRst ^ a[i]);flag = LastBitof1(flag); //只保留最右边为1的位//求firstfirst = second = third = 0 ;vector<int> vec;for(i=0 ; i<len ;++i)if(LastBitof1( xorRst ^ a[i] ) == flag )//x^a的第m位为1的情况 这里的m位其实是最右边为1位first ^= a[i];  //这样first 就求出了for(i=0 ; i<len ;++i)if( a[i] == first)Swap(a[i],a[len-1]);//移除该first值FindTwoOnceNum(a,len-1,second,third);RstState = true; }return RstState ;}void printArray(int a[],int len){if( a != NULL && len > 0){for(int i=0 ; i<len; ++i)cout<<a[i]<<" ";cout<<"\n";}}void Test1(){int a[] = {1,2,3,4,3,2,1};int len = sizeof(a)/sizeof(int);cout<<"数组中1个出现1次的数:\n";cout<<"数组元素为:\n";printArray(a,len);cout<<"所求值为: "<<FindSingleNum(a,len)<<endl;cout<<"---------------------------\n";}void Test2(){int a[] = {1,2,3,4,3,2,1,9};int len = sizeof(a)/sizeof(int);cout<<"数组中2个出现1次的数:\n";cout<<"数组元素为:\n";printArray(a,len);int first ,second;FindTwoOnceNum(a,len,first,second);cout<<"所求值为: "<<first<<" "<<second<< endl;cout<<"---------------------------\n";}void Test3(){int a[] = {1,2,3,4,3,2,1,9,7};int len = sizeof(a)/sizeof(int);cout<<"数组中3个出现1次的数:\n";cout<<"数组元素为:\n";printArray(a,len);int first ,second,third;FindTreeOnceNum(a,len,first,second,third);cout<<"所求值为: "<<first<<" "<<second<<" " <<third<< endl;}int main(){Test1();Test2();Test3();return 0 ;}

六.参考资料

http://zhedahht.blog.163.com/blog/static/25411174201283084246412/何海涛的博客 剑指offer作者


0 0
原创粉丝点击