下面谈一下异或的强大之处,总结来自July大神微博推荐站点的一篇博客,虽然博主是web开发人员,但是这篇日志数学技巧极强
http://www.lijinma.com/blog/2014/05/29/amazing-xor/
先来一道Leetcode通过率最高的题目,求一个数组中尽出现一次的数字,其他数字均出现两次。
一看通过率最高,估计最简单,然而本屌想破脑袋也没想到可以在O(n)时间O(1)空间的解法,有时还真的怀疑自己的智商了,但是从某July大神推荐的站点博客看到解法后,
三关禁毁,这尼玛我没怎么用过位运算的DS压根也想不到用异或来解。所以,我再次坚信,某些数学题,其实看到解法都觉得容易理解,但是你自己想出来的难度又是另当别论,有点类似于从起点找一条路径到终点,而每前进一步都有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;
}