在学习算法过程中,第一次接触到了位运算异或及其一些用法,感觉非常妙,写一篇文章来整理一下。
异或:
性质:a^a=0; a^0=a; 满足交换律和结合律
下面我们来看一道妙用异或的题:
- 给定一个数组,这个数组中有一个数字出现过奇数次,而其他数字出现过偶数次,找出这个数字。
思路:根据异或的结合律,可以将数组中所有数字从头到尾进行异或,偶数次的数字两两异或为0,最后只剩下出现奇数次的数字。
public static int xor(int[] arr) { int eor = 0; for (int i : arr) { eor ^= i; } return eor; }
下面我们看一下这道题的升级版
- 给定一个数组,其中有两个出现奇数次的数字,其余均出现偶数次,找出这两个数字。
- 思路:同样根据异或的结合律,把数组从头到尾异或一遍,这里假设两个奇数次数字为a和b,则异或结果eor=a^b,a和b一定不相等,则eor一定不为零,则eor至少有一位为1,假设是第n位(选择eor中最靠右的那个1,原因后面会说明),下面,根据第n位,对整个数组进行分类。
第n位为0 | 第n位为1 |
---|---|
部分偶数次数 | 部分偶数次数 |
a | b |
于是,我们可以对某一类进行全部异或,比如在第n位为1类上进行异或,最后结果为eor1=b,另一个数b自然可通过eor^eor1得到。
public static int[] xor2(int[] arr) {
int eor = 0;
for (int i : arr) {
eor ^= arr[i]; //-> eor = a^b
}
int rightOne = eor & (~eor + 1);
int eor1 = 0;
for (int i = 0; i < arr.length; i++) {
if ((arr[i] & rightOne) == rightOne) { //在该位上是1的
eor1 ^= arr[i];
}
}
return new int[]{eor1, eor1 ^ eor};
}
别小看了这段代码,在这段代码中,还有两个位运算的妙用:
int rightOne = eor & (~eor + 1);
这行代码意为将eor二进制最靠右的1取出来赋给rightOne,目前可先记忆,实际上与补码的性质有关。(arr[i] & rightOne) == rightOne
这行代码意为筛选出数组中第n位为1的数字。
下面我们来看另一道题:
给定一个数,判断其是否是2的幂次方。
思路:我们可以想到,一个数如果是2的幂次方,例如4,8,16,其二进制必然只有一个1,即100,1000,10000,所以我们只需利用这点去判断即可,如果该数n二进制位上只有一个1,则n&(n-1)==0
public static boolean if2(int n){ if((n&(n-1))==0){ return true; } return false; }
位运算的另一个用途,是进行运算优化加速:
接下来介绍我们经典的n皇后问题:
- 题目:给定一个整数N,要求在N*N的棋盘上摆放N个皇后,且这N个皇后任意两个都不在同一行、同一列、同一斜线上
常规解法:暴力递归尝试,按行尝试,逐个尝试所有位置。
现可使用位运算优化,虽然时间复杂度不变,但其中常数大大降低,可有效提高效率。
假设现在n=8,则可使用8位二进制数代替当前皇后所在位置
我们假设在第一行这样放:00001000(1代表皇后所在位置)
则,对于第二行,我们即可给定一串数字去限定皇后可放的位置
行数 | 二进制数字限制 |
---|---|
1 | 00001000 |
2 | 00011100 |
3 | 00111110 |
上表中2、3行1位置即皇后不可放的位置(共列、共斜线),选取1作为限制位置的原因是可以通过上一行的简单移位得到。以这种策略进行下去可有效提升效率。
此外,位运算还有许多妙用,比如位图、布隆过滤器等,在以后的文章中会重点介绍。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。