zoukankan      html  css  js  c++  java
  • 【40讲系列12】位运算

    一、理论

    常用的位运算:

    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补齐
            }
        }
    }
  • 相关阅读:
    微软外服 AlI In One
    js 循环多次和循环一次的时间的性能对比 All In One
    vue inject All In One
    Excel 表格数据倒置 All In One
    SVG tickets All In One
    OH MY ZSH All In One
    js array for loop performance compare All In One
    mac terminal show You have new mail All In one
    新闻视频 26 制作母版页
    转自牛腩 母版页和相对路径
  • 原文地址:https://www.cnblogs.com/HuangYJ/p/14026965.html
Copyright © 2011-2022 走看看