一、理论
常用的位运算:
X & 1 == 1 OR == 0 :判断奇偶
X = X & (X - 1) : 清零最低位的1 (常用来求二进制位有多少个1)
X & -X : 得到最低位的1, -X就是按位取反末尾加1,。 例如X为10100,-X为01100
X & (1 << (n - 1)) : 获取 X 的第 n 位的幂值
二、典型例题
①:二进制数中的比特位统计问题(LC191、剑指11.二进制中1的个数)
思路:利用X = X & (X - 1) ,每次清零最低位的1.
②:判断一个数是否是2的幂次方(LC231)
思路:一个数如果是2的幂次方,其二进制位有如下特点:有且仅有一个1。
class Solution { public boolean isPowerOfTwo(int n) { return n > 0 && (n & (n - 1)) == 0; } }
③:比特位计数(LC338)
class Solution { public int[] countBits(int num) { // 方法1 和 方法2 是最优解,它们都是利用数组前面已经算好的数来计算当前数的1的个数 // 方法1:i & (i - 1) 可以清零最低位的1。 int[] res = new int[num + 1]; for (int i = 1; i <= num; i++) { res[i] = res[i & (i - 1)] + 1; } return res; // 方法2:当i的最低位是1,i中1的个数是i>>1中1的个数再加1;如果i最低位是0,则加0 /*int[] res = new int[num + 1]; for (int i = 1; i <= num; i++) { res[i] = res[i >> 1] + (i & 1); } return res;*/ /* 方法3:遍历1~num,调用函数计算每一个数的 1的个数。 int[] res = new int[num + 1]; for (int i = 1; i <= num; i++) { res[i] = numberOf1(i); } return res; */ } /** * LeetCode 191 */ public int numberOf1(int n){ int count = 0; while (n != 0){ count++; n = n & (n - 1); } return count; } }
☆☆☆☆④:N皇后问题的位运算解法(LC52)
不使用位运算(执行耗时:2 ms,击败了61.47% 的Java用户),其解法见:【40讲系列9】剪枝
使用位运算加速才是本题的最优解,(执行耗时:0 ms,击败了100.00% 的Java用户)
位运算版本1:(推荐手撕~)
class Solution { int res = 0; public int totalNQueens(int n) { if (n < 1 || n > 32) return 0; dfs(0,0,0,0, n); return res; } private void dfs(int row, int col, int pie, int na, int n) { if (row == n){ res++; return; } // 得到当前所有的有效空位。 // (~(col | pie | na))表示获取当前空位,标识为1。由于是32位整数,高位也全都是1. // 因此需要 & ((1 << n) - 1) 去掉高位,得到有效位置上的空位。例如8皇后,即末尾8位全为1 int pos = (~(col | pie | na)) & ((1 << n) - 1); while (pos != 0){ // 遍历所有的空位 // 获取最后的空位1。 执行后相当于该空位已经被占了,可以到下一行了 int mostRightOne = pos & (-pos); // (-pos)即(~pos+1), 按位取反末尾加1 // int mostRightOne = pos & (~pos + 1); pos &= (pos - 1); // 去掉最低位的1 // pos = pos - mostRightOne; dfs(row + 1,(col | mostRightOne), // col、pie、na都要受到mostRightOne的影响,进行更新 (pie | mostRightOne) << 1, (na | mostRightOne) >> 1,n); } } }
位运算版本2:(参考左神)
class Solution { // 参考左神P240 int res = 0; public int totalNQueens(int n) { if (n < 1 || n > 32) return 0; int upperLim = n == 32 ? -1 : (1 << n) - 1; // -1的二进制表示为32位全为1 help(upperLim,0,0,0); return res; } /** help():剩余的皇后在之前皇后的影响下,有多少种合法的摆法。 * * @param upperLim 表示当前行哪些位置可以放皇后,1代表可以,0代表不能. 在递归过程中始终不变 * @param colLim 表示递归计算到上一行为止,在哪些列上已经放置了皇后,1代表已经放置,0代表没有放置 * @param leftDiaLim 表示递归计算到上一行为止,受已经放置的皇后的左下方斜线的影响,导致当前行不能放置的位置 * 1代表不能放置 * leftDiaLim每次左移一位,就可以得到之前所有皇后的左下方斜线对当前行的影响。 * @param rightDiaLim 表示递归计算到上一行为止,受已经放置的皇后的右下方斜线的影响,导致当前行不能放置的位置 * 1代表不能放置 * rightDiaLim每次右移一位,就可以得到之前所有皇后的右下方斜线对当前行的影响。 */ private void help(int upperLim, int colLim, int leftDiaLim, int rightDiaLim){ if (colLim == upperLim){ res++; return; } int pos = 0; // 代表当前行在colLim、leftDiaLim、rightDiaLim的影响下,还有哪些位置是可选择的。1代表可以选择 int mostRightOne = 0; // 代表在pos中,最右边的1是在什么位置。然后从右到左一次筛选出pos中可选择的位置进行递归尝试。 pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim)); while (pos != 0){ mostRightOne = pos & (~pos + 1); pos = pos - mostRightOne; help(upperLim,colLim | mostRightOne, (leftDiaLim | mostRightOne) << 1, (rightDiaLim | mostRightOne) >>> 1); // >>>无符号右移,忽略符号位,空位都以0补齐 } } }