位运算符之异或的化腐朽为奇妙
以下谈一下异或的强大之处。总结来自July大神微博推荐网站的一篇博客。尽管博主是web开发者。可是这篇日志数学技巧极强
http://www.lijinma.com/blog/2014/05/29/amazing-xor/
先来一道Leetcode通过率最高的题目,求一个数组中尽出现一次的数字。其它数字均出现两次。
一看通过率最高,预计最简单,然而本屌想破脑袋也没想到能够在O(n)时间O(1)空间的解法。有时还真的怀疑自己的智商了。可是从某July大神推荐的网站博客看到解法后,
三关禁毁。这尼玛我没怎么用过位运算的DS压根也想不到用异或来解。所以,我再次坚信,某些数学题,事实上看到解法都认为easy理解。可是你自己想出来的难度又是另当别论。有点类似于从起点找一条路径到终点。而每前进一步都有N多种数学工具、算法能够使用,你的搜索空间已经是指数级的了。所以做的题目一定要达到一定规模,才干有感觉。
首先简略说明异或运算的一点性质,当你学的时候,压根认为就是数学理论,你根本不知道他干嘛用:
1^X=1
0^X=X
A^B=B^A
A^B^C=A^(B^C)
对于上面的错误,博主表示很抱歉。犯了低级错误。为表示歉意及督促作用,博主放在这里吃一堑 长一智
应该是1^X=~X 实在抱歉 博主写得太快,一下激动犯错了
另外以下的找两个单独出现一次的数的问题,主要是运用了0^X=X 的性质,将其它的都成对异或为0,仅仅剩下a或b, 然后0^X=X 所以异或就出结果了: )
上面的性质都是显然的。回到这道leetcode题,有了这个就有个惊人发现:
A^B^C^C^A^D^B=A^A^B^B^C^C^D=D!!!!(你能够多次运用交换律。结合律获得这一性质)
没错,仅仅要所有异或,就是要的答案。代码不能再简单了。附上本吊的代码:
int singleNumber(int A[], int n) { for(int i=1;i<n;i++) { A[0]^=A[i]; } return A[0]; }
博主的代码还不够优化,由于不须要保留原数组值,且通过分析,不会有n=0的情况
所以这道高AC率不是由于算法简单,而是知道算法后代码简单,差点儿没有不论什么bug的陷阱,
另外附上一道经典的算法题。不用不论什么空间交换a b的值,这个差点儿大家都傻眼,都是想用tmp操作实现的,代码例如以下:
a=a^b; b=a^b;//b= a^b ^b=a, assigned a to b a=a^b;//a=a^b ^a=b, assigned b to a
今天发现了这段代码有一个非常重要的被忽视的地方,就是前提是a b确实是两个不同的变量,假设是同样的变量就跪了。。
。。
我今天写Permutation意识兴起,用用这个***诈天的算法,发现出bug了,原来是由于permutation里面有自己和自己交换,事实上也就是不须要交换了。当然假设外部处理好了是一样的。
假设用tmp变量的话。甚至能够处理这样的'自交换'操作。下次面试官问到这个和他炫耀一下自己理解这样的牛逼算法的一个缺陷~~~~~
问题就这样被异或运算奇妙的攻克了。
另外再附上一个解法,在google面试题看到的。暴露地址的方式来交换,这种话引用传递应该也能够的
void foo(int*a, int* b) { *a = *a+*b; *b = *a-*b; *a = *a-*b; }
然后如今回忆起来原来都看过这个题目及解法(不用变量交换两个变量的值)。仅仅是当时没有summary和write下来。如今总结在这里。只是后者可能会有溢出,所以位运算是最好的算法.这里假设大家刚開始接触程舍可能会非常费解。和数学思维非常不同。我当初也是问C++老师,交换ab不久a=b b=a 为啥还要tmp,老师说会覆盖掉原值(好吧,请原谅我那时的幼稚==),由于程舍是计算机科学。计算机科学都是以冯诺依曼机模型,是须要存储,输入输出的。和数学光在脑子里思考不一样,习惯之后就好了。
以下在扩展一道题目,从一个仅仅有两个数字仅出现一次,其它数字都出现两次的数组中找出这两个出现一次的数字第一反应又是抑或运算,然而所有异或得到的是a^b, 怎么把他们分开,又联想到交换a b值的case,这次会复杂一些,解法是先看a^b值中位为1的那么位置。
发现这些位置a b不同,其它位置都同样。而其它2*n个运算异或结果为0。因此通过异或这一位为1的数。就能够得到a 或b了(大家能够思考下为什么),假如得到a, 再a^ (a^b)就得到b了。最难想的就是怎么先得到a b 中的一个
贴上别人的代码。当中选择了低位中第一个为1的位置,事实上不论什么一个1都是能够的,并且里面求000001还用了点位运算知识。num&~(num-1)
int getFirstOneBit(int num) //输出 num 的低位中的第一个 1 的位置 { return num & ~(num - 1); // num 与 -num 相与找到 } void findTwo(int *array, int length){ int aXORb = 0; int firstOneBit = 0; int a = 0; int b = 0; for (int i = 0; i < length; i++) { aXORb ^= array[i]; } assert(aXORb != 0); //保证题目要求,有两个single的数字 firstOneBit = getFirstOneBit(aXORb); for (int i = 0; i < length; ++i) { if(array[i] & firstOneBit) { a ^= array[i]; } } b = aXORb ^ a; cout << "a: " << a << endl; cout << "b: " << b << endl; }