Leetcode 题解 - 位运算

179 阅读3分钟

原理

基本原理

0s 表示一串 0,1s 表示一串 1。

  ^ 异或		    & 与			   | 或
x ^ 0s = x      x & 0s = 0      x | 0s = x
x ^ 1s = ~x     x & 1s = x      x | 1s = 1s
x ^ x = 0       x & x = x       x | x = x

利用 x ^ 1s = ~x 的特点,可以将一个数的位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。

1^1^2 = 2

利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask:00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。

01011011 &
00111100
--------
00011000

利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。

01011011 |
00111100
--------
01111111

位与运算技巧

n&(n-1) 去除 n 的位级表示中最低的那一位 1。例如对于二进制表示 01011011,减去 1 得到 01011010,这两个数相与得到 01011010。

01011011 &
01011010
--------
01011010

n&(-n) 得到 n 的位级表示中最低的那一位 1。-n 得到 n 的反码加 1,也就是 -n=~n+1。例如对于二进制表示 10110100,-n 得到 01001100,相与得到 00000100。

10110100 &
01001100
--------
00000100

n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1,和 n&(n-1) 效果一样。

移位运算

>> n 为算术右移,相当于除以 2n,例如 -7 >> 2 = -2。

11111111111111111111111111111001  >> 2
--------
11111111111111111111111111111110

>>> n 为无符号右移,左边会补上 0。例如 -7 >>> 2 = 1073741822。

11111111111111111111111111111001  >>> 2
--------
00111111111111111111111111111111

<< n 为算术左移,相当于乘以 2n。-7 << 2 = -28。

11111111111111111111111111111001  << 2
--------
11111111111111111111111111100100

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)。

Java 中的位操作

static int Integer.bitCount();           // 统计 1 的数量
static int Integer.highestOneBit();      // 获得最高位
static String toBinaryString(int i);     // 转换为二进制表示的字符串

1. 汉明距离

Leetcode (opens new window)/ 力扣

(1)调用 API

class Solution {
    public int hammingDistance(int x, int y) {
        // ^ 不同为 1,相同为 0
        return Integer.bitCount(x ^ y);
    }
}

(2)找最低位

class Solution {
    public int hammingDistance(int x, int y) {
        // ^ 不同为 1,相同为 0
       int temp = x ^ y;
       int result = 0;
       while (temp != 0) {
           result += temp & 1;
           temp >>>= 1;
       }

       return result;
    }
}

(3)Brian Kernighan 算法

class Solution {
    public int hammingDistance(int x, int y) {
        // ^ 不同为 1,相同为 0
       int temp = x ^ y;
       int result = 0;
       while (temp != 0) {
           temp &= temp - 1;
           result++;
       }

       return result;
    }
}


2.只出现一次的数字

Leetcode (opens new window)/ 力扣

两个相同的数异或为 0。x ^ x = 0

class Solution {
    public int singleNumber(int[] nums) {
        int result = 0;
        for (int num : nums) {
            result ^= num;
        }

        return result;
    }
}

3. 丢失的数字

Leetcode (opens new window)/ 力扣

class Solution {
    public int missingNumber(int[] nums) {
        int result = 0;

        for (int i = 0; i < nums.length; i++) {
            result ^= i ^ nums[i];
        }

        return result ^ nums.length;
    }
}


4. 只出现一次的数字 III

Leetcode (opens new window)/ 力扣(opens new window)

两个不相等的元素在位级表示上必定会有一位存在不同。

将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。

sum &= -sum 得到出 sum 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。

class Solution {
    public int[] singleNumber(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum ^= num;
        }
        sum &= -sum;

        int[] result = new int[2];
        for (int num : nums) {
            if ((sum & num) == 0) {
                result[0] ^= num;
            } else {
                result[1] ^= num;
            }
        }

        return result;
    }
}

5. 翻转一个数的比特位

Leetcode (opens new window)/ 力扣



6. 不用额外变量交换两个整数

a = a ^ b;
b = a ^ b;
a = a ^ b;


7. 判断一个数是不是 2 的 n 次方

Leetcode (opens new window)/ 力扣

class Solution {
    public boolean isPowerOfTwo(int n) {
        // n = 8 = 1000
        // n = 8 - 1 = 0111
        // 所以它们 1 的位置一定是错开的
        if (n > 0 && (n & (n - 1)) == 0) {
            return true;
        }

        return false;
    }
}