1. 程式人生 > 實用技巧 >【40講系列11】位運算

【40講系列11】位運算

一、理論

常用的位運算:

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 {
    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使用者)

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補齊
        }
    }
}