【40講系列11】位運算
阿新 • • 發佈:2020-11-24
一、理論
常用的位運算:
X & 1 == 1 OR == 0 :判斷奇偶
X = X & (X - 1) : 清零最低位的1 (常用來求二進位制位有多少個1)
X & (1 << (n - 1)) : 獲取 X 的第 n 位的冪值
二、典型例題
①:二進位制數中的位元位統計問題(LC191、劍指11.二進位制中1的個數)
思路:利用X = X & (X - 1) ,每次清零最低位的1.
②:判斷一個數是否是2的冪次方(LC231)
思路:一個數如果是2的冪次方,其二進位制位有如下特點:有且僅有一個1。
class Solution { publicboolean 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使用者)
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補齊 } } }