本文讲解位运算相关原理
原理
基本原理
0s 表示一串 0,1s 表示一串 1。
1 | x ^ 0s = x x & 0s = 0 x | 0s = x |
利用 x ^ 1s = ~x 的特点,可以将一个数的位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。
1 | 1^1^2 = 2 |
利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask:00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。
1 | 01011011 & |
利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。
1 | 01011011 | |
位与运算技巧
n&(n-1) 去除 n 的位级表示中最低的那一位 1。例如对于二进制表示 01011011,减去 1 得到 01011010,这两个数相与得到 01011010。
1 | 01011011 & |
n&(-n) 得到 n 的位级表示中最低的那一位 1。-n 得到 n 的反码加 1,也就是 -n=~n+1。例如对于二进制表示 10110100,-n 得到 01001100,相与得到 00000100。
1 | 10110100 & |
n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1,和 n&(n-1) 效果一样。
移位运算
>> n 为算术右移,相当于除以 2n,例如 -7 >> 2 = -2。
1 | 11111111111111111111111111111001 >> 2 |
>>> n 为无符号右移,左边会补上 0。例如 -7 >>> 2 = 1073741822。
1 | 11111111111111111111111111111001 >>> 2 |
<< n 为算术左移,相当于乘以 2n。-7 << 2 = -28。
1 | 11111111111111111111111111111001 << 2 |
mask 计算
要获取 111111111,将 0 取反即可,~0。
要得到只有第 i 位为 1 的 mask,将 1 向左移动 i-1 位即可,1<<(i-1) 。例如 1<<4 得到只有第 5 位为 1 的 mask :00010000。
要得到 1 到 i 位为 1 的 mask,(1<<i)-1 即可,例如将 (1<<4)-1 = 00010000-1 = 00001111。
要得到 1 到 i 位为 0 的 mask,只需将 1 到 i 位为 1 的 mask 取反,即 ~((1<<i)-1)。
对于 10000000 这样的数要扩展成 11111111,可以利用以下方法:
1 | mask |= mask >> 1 11000000 |
Java 中的位操作
1 | static int Integer.bitCount(); // 统计 1 的数量 |
不用额外变量交换两个整数
1 | a = a ^ b; |
位运算相关算法案例
1.实现整数的加法(Sum of Two Integers)
a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。
递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。
1 | public int getSum(int a, int b) { |
2.数组中唯一一个不重复的元素
1 | Input: [4,1,2,1,2] |
两个相同的数异或的结果为 0,对所有数进行异或操作,最后的结果就是单独出现的那个数。
1 | public int singleNumber(int[] nums) { |
3.找出数组中缺失的那个数
描述:数组元素在 0-n 之间,但是有一个数是缺失的,要求找到这个缺失的数。
1 | Input: [3,0,1] |
代码:
1 | public int missingNumber(int[] nums) { |
4.数组中不重复的两个元素
两个不相等的元素在位级表示上必定会有一位存在不同。
将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。
diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。
1 | public int[] singleNumber(int[] nums) { |
5.求一个数的补码
描述:不考虑二进制表示中的首 0 部分。
对于 00000101,要求补码可以将它与 00000111 进行异或操作。那么问题就转换为求掩码 00000111。
method1:
1 | public int findComplement(int num) { |
**method2:**可以利用 Java 的 Integer.highestOneBit() 方法来获得含有首 1 的数。
1 | public int findComplement(int num) { |
**method3:**对于 10000000 这样的数要扩展成 11111111,可以利用以下方法:
1 | mask |= mask >> 1 11000000 |
1 | public int findComplement(int num) { |
6.判断一个数的位级表示是否不会出现连续的 0 和 1
对于 1010 这种位级表示的数,把它向右移动 1 位得到 101,这两个数每个位都不同,因此异或得到的结果为 1111。
1 | public boolean hasAlternatingBits(int n) { |
7.翻转一个数的比特位
method1:
1 | public int reverseBits(int n) { |
method2: 如果该函数需要被调用很多次,可以将 int 拆成 4 个 byte,然后缓存 byte 对应的比特位翻转,最后再拼接起来。
1 | private static Map<Byte, Integer> cache = new HashMap<>(); |