从面试题中学算法(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作者
- 从面试题中学算法(2)---求数组中唯一n个出现1次的数字(n=1,2,3)
- Google经典面试题:求从1到n的n个整数中,字符“1”出现的个数
- 题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。(google面试题) 例如 输入12,从1到12这些整数中包含1 的数字有1,10,11和12,1一共出现了5次。 分析
- 总结篇:数组中N(n=1,2,3)个只出现一次的数字[find N numbers which appear only once in array]
- 剑指offer面试题 求从1到n整数中1出现的次数
- 【算法】数组中只出现1次的两个数字(百度面试题)
- 面试题:元素为32位整数的数组中只有一个数字出现2次,其余都是出现3次,求这个数
- 算法 剑指Offer 面试题32 从1到n整数中1出现的次数
- 从面试题中学算法(1)--哈希表查找字符串中第一次仅出现一次的字母
- 算法题1 数组中唯一出现1次的数|唯一出现2次的数
- N个元素的数组中找出出现多于N/2次的数(主元素)
- 找出数组{1,2,3,4,...N-1}中出现的唯一重复数
- 从有n个元素的数组中找出出现次数大于n/3次的元素
- 数组中唯一出现1次的数|唯一出现2次的数
- 程序员面试题精选100题:求从1到n的正数中1出现的次数
- 【剑指Offer学习】【面试题32:求从1到n的整数中1出现的次数】
- 给定一个包含从0,1,2,...,n中取出的n个不同数字的数组,找到数组中缺少的数字。
- 找出2n个数字重复n次出现的数字
- A. Appleman and Easy Task 8-26
- Tchart的横纵坐标最大和最小值如何用语句设置Tchart的横纵坐标最大和最小值
- 遍历Map四种
- Hibernate4实战之注解版
- 【再曝猛料】 Virglass手机影院系列产品即将诞生
- 从面试题中学算法(2)---求数组中唯一n个出现1次的数字(n=1,2,3)
- Android ViewPager+Fragment,标示跟着ViewPager的滑动而同时滑动
- 每天一个linux命令:Linux 目录结构
- J2EE应用的五种核心策略以及实现
- opencv中cvLoadImage和cvCloneImage的内存泄露问题
- linux设备驱动程序之简单字符设备驱动
- JSP标签库大全
- 如何根据sid查看改会话具体执行的是什么sql语句
- 通过CMake在VS中查看OpenCV源码