您的位置:首页 > 职场人生

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

2014-08-27 11:29 387 查看
       题目要求:给定一个整数数组,其中只有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 1
first = second = 0 ;
for(int j=0 ;j<len ;++j)
{
if(a[j] & xorRst ) //通过xorRst 来分组
first ^= a[j];
else
second ^= 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的位
//求first
first = 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 1 first = second = 0 ; for(int j=0 ;j<len ;++j) { if(a[j] & xorRst ) //通过xorRst 来分组 first ^= a[j]; else second ^= 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的位 //求first first = 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作者
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐