【必备算法】位运算:LeetCode(一) 191. 位1的个数,231. 2的幂,190. 颠倒二进制位,371. 两整数之和

360 阅读5分钟

在看位运算相关LeetCode题之前,先放个传送门 常见位运算符及规律...

191. 位1的个数¹

编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。

示例 1:

输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'

示例 2:

输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'

示例 3:

输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'

解法一:位运算

  • 思路:没啥说的,通过一个count记录1的个数,问题就落在了如何判断当前位是否为1((x << n) & 1)
  • 复杂度:
    • Time:O(1),循环的次数是确定(32),与数据规模无关
    • Space:O(1)
public int hammingWeight(int n) {
        int count =  0;
        // 因为是4字节,所以长度是确定的:32位
        for (int i = 0; i < 32; i++) {
        	// 判断当前位是否为1
            if ((n & 1) != 0) count++;
            // n 右移来将下一位放到最后
            n >>= 1;
        }
        return count;
}

231. 2的幂¹

给定一个整数,编写一个函数来判断它是否是 2 的幂次方。

示例 1:

输入: 1
输出: true
解释: 20 = 1

示例 2:

输入: 16
输出: true
解释: 24 = 16

示例 3:

输入: 218
输出: false

解法一:暴力循环

  • 思路:不断除2,直到除不了2(%2 != 0),然后判断这个奇数是否为1
  • 复杂度:
    • Time:O(n)
    • Space:O(1)
public boolean isPowerOfTwo(int n) {
        if (n == 0) return false;
        while (n % 2 == 0) n /= 2;
        return n == 1;
}

解法二:位运算

  • 思路:若 n = 2^x 且 x 为自然数(即 n 为 2 的幂),则一定满足以下条件

    • 恒有 n & (n - 1) == 0,这是因为:
      • n 二进制最高位为 1,其余所有位为 0;比如 2º=0001,2¹=0010,2²=0100
      • n - 1 二进制最高位为 0,其余所有位为 1;比如 2º-1=0000,2¹-1=0001,2²-1=0011
    • 一定满足 n > 0
  • 复杂度:

    • Time:O(1)

    • Space:O(1)

public boolean isPowerOfTwo(int n) {
        return n > 0 ? (n & (n-1)) == 0 : false;
}

位运算符是直接就用二进制运算,不一定运算数就是二进制形式,也可以像本题是十进制形式;底层都是二进制运算

190. 颠倒二进制位¹

颠倒给定的 32 位无符号整数的二进制位。

示例 1:

输入: 00000010100101000001111010011100
输出: 00111001011110000010100101000000
解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
      因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。

示例 2:

输入:11111111111111111111111111111101
输出:10111111111111111111111111111111
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
      因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。

解法一:转成字符串

  • 思路:将数字变成字符串去操作
    1. 将 n 转化成String
    2. 对字符串进行逆序操作
    3. 将字符串还原成int
  • 复杂度:
    • Time:O(1)
    • Space:O(1)

解法二:位运算

  • 思路:可以通过位运算,得到n的每一位,然后逆序组织ans
    1. 从 n 的最低位开始,获取 n 的所有二进制位,并不断右移
    2. 从 ans 的最高位开始,加上n的二进制位,并不断左移
  • 复杂度:
    • Time:O(1)
    • Space:O(1)
public int reverseBits(int n) {
        int ans = 0;
        for (int i = 0; i < 32; i++) {
            ans = (ans << 1) + (n & 1); // ans左移,不断给低位赋值
            n >>= 1; // n右移,不断获取高位
        }
        return ans;
}

371. 两整数之和¹

不使用运算符 +- ,计算两整数 ab 之和。

示例 1:

输入: a = 1, b = 2
输出: 3

示例 2:

输入: a = -2, b = 3
输出: 1

解法一:位运算-迭代

  • 思路:既然不能使用 + - ,那肯定就是用位运算

    1. 不进位加:( 0 0 = 0),(0 1 = 1),(1 0 = 1),(1 1 = 0)-----> 异或(^)
    2. 进位:(1 1 = 1),(0 1 = 0),(1 0 = 0),(0 0 = 0)-----> 与(&)------> 左移一位(<<)

    ====》sum + carry 就能得到结果,但不能 + ,所以不断重复以上步骤,直到carry=0(无进位)

  • 复杂度:

    • Time:O(n)
    • Space:O(1)
public int getSum(int a, int b) {
        while(b != 0) {
            int sum = a ^ b;  // 无进位加法结果
            int carry = (a & b) << 1;  // 进位值
            a = sum;  // 因为不能相加,所以再重复以上步骤
            b = carry;
        }
        return a;
    }

解法二:位运算-递归

public int getSum(int a, int b) {
        return b == 0 ? a : getSum(a ^ b, (a & b) << 1);
    }