算法通关村十一关:位运算高频算法题

位运算:高频算法题

位移的妙用

位1的个数

LeetCode 191
https://leetcode.cn/problems/number-of-1-bits/

方法1:

思路分析
32位二进制表示的整数,每一位进行判断,同时进行统计

代码实现

class Solution:
    def hammingWeight(self, n: int) -> int:
        count = 0
        for i in range(32):
            if n & (1<<i) != 0:
                count += 1
        return count

方法2:

思路分析;
运用性质,将n的二进制表示的最后一个1变成0:n&(n-1)
每次执行 n=n&(n-1),n就会少一个1,可重复循环执行,直到n==0,循环次数即为1的个数

代码实现

class Solution:
    def hammingWeight(self, n: int) -> int:
        count = 0
        while n!= 0:
            n = n & (n-1)
            count += 1
        return count

总结:
方法1循环次数取决于原始数字的位数,方法2循环次数取决于1的个数,方法2比方法1效率更高

比特位计数

LeetCode 338. 比特位计数
https://leetcode.cn/problems/counting-bits/description/

思路分析:
参考上述 位1的个数

代码实现

class Solution:
    def countBits(self, n: int) -> List[int]:
        # 方法1:硬解
        bits = [0]*(n+1)
        for i in range(n+1):
            for j in range(32):
                bits[i] += (i & (1<<j) != 0)
        
        return bits
class Solution:
    def countBits(self, n: int) -> List[int]:
        # 方法2:比特位计数
        bits = [0]*(n+1)
        for i in range(n+1):
            k = i
            while i!=0:
                i=i&(i-1)
                bits[k] += 1
        return bits

颠倒无符号整数

LeetCode 190. 颠倒二进制位
https://leetcode.cn/problems/reverse-bits/

思路分析

方法1:
n的二进制表示的从低到高第i位,在颠倒之后变成了第 31-i (0≤i<32)
从低到高遍历n的二进制表示的每一位,将其放在其在颠倒之后的位置,最后相加即可

举例:看个16位的演示下

原始数据 1001 1111 0000 0110 (低位)
第一步:获得n的最低位0,将其右移16-1=15位,得到
reversed:0*** **** **** ****
n右移1位:0100 1111 1000 0011

第二步:继续获得n的最低位1,将其右移15-1=14位,并与reversed相加得到:
reversed:01** **** **** ****
n右移1位:0010 0111 1100 0001

继续,一直到n全部变成0

方法2:分块思想
n的二进制有32位,将n的二进制表示分成较小的块,然后将每个块的二进制位分别颠倒,最后将每个块的结果合并得到最终结果

代码实现

方法1

class Solution:
    def reverseBits(self, n: int) -> int:
        result = 0
        power = 31
        while n:
            result += ((n&1)<<power)
            power-=1
            n>>=1
        return result 

注:python不用考虑溢出问题

if __name__ == '__main__':
    n = 28  # 1 1100
    print(type(n), n)  # <class 'int'> 28   1 1100
    print(type(n << 31), (n << 31))  # <class 'int'> 60129542144    1110 0000 0000 0000 0000 0000 0000 0000 0000 (36位)
    print(type(n << 63), (n << 63))  # <class 'int'> 258254417031933722624
    # 1110 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 (68位)

方法2:

public class Solution {
    // you need treat n as an unsigned value
    public int reverseBits(int n) {
        n = (n>>>16) | (n<<16);
        n = ((n & 0xff00ff00) >>> 8) | ((n & 0x00ff00ff) << 8);
        n = ((n & 0xf0f0f0f0) >>> 4) | ((n & 0x0f0f0f0f) << 4);
        n = ((n & 0xcccccccc) >>> 2) | ((n & 0x33333333) << 2);
        n = ((n & 0xaaaaaaaa) >>> 1) | ((n & 0x55555555) << 1);
        return n;
    }
}

位实现加减乘除

计算机中,位运算的效率比加减乘除效率更高,在高性能软件的源码中大量应用,计算机里各种运算本质上都是位运算

位运算实现加法

LeetCode371. 两整数之和
https://leetcode.cn/problems/sum-of-two-integers/

思路分析

两个二进制位相加的情况


[1]	0 + 0 = 0
[2]	0 + 1 = 1
[3]	1 + 0 = 1
[4]	1 + 1 = 0	(发生了进位,实际10)

两个位加的时候,需要考虑两个问题:

  1. 进位部分是什么?
  2. 不进位部分是什么?

进位部分:
都是1的情况才进位 => a&b
然后位数由1位变成两位 => 左移一位,(a&b)<<1
不进位部分:
相同为0,不相同为1 => a^b

结论

  • 不进位部分:用a^b计算
  • 是否进位,以及进位值,使用(a&b)<<1计算

于是,我们可以将整数 a 和 b 的和,拆分为a和b的无进位加法结果与进位结果的和

代码实现

java实现

class Solution {
    public int getSum(int a, int b) {
        while(b != 0){
            int sign = (a&b)<<1;
            a = a^b;
            b = sign;
        }
        return a;
    }
}

python实现
python中没有溢出,需要特殊处理

class Solution:
    def getSum(self, a: int, b: int) -> int:
        # python没有溢出,需要特殊处理
        x = 0xffffffff
        a, b = a&x, b&x
        while b != 0:
            a, b = (a^b), ((a&b)<<1)&x
        return a if a<= 0x7fffffff else ~(a^x)

注:取正数(补码)代表的负数补码的值

~(a^0xfff)    注:f格式视具体情况而定

1111 1111 1110 正数补码表示 4094
1111 1111 1110 负数补码表示 -2

>>>~(4094^0xfff)
-2

递归乘法

LeetCode 面试题 08.05. 递归乘法
https://leetcode.cn/problems/recursive-mulitply-lcci/

思路分析

得到A和B中最小值和最大值,将其中最小值当做乘数,选最小值当乘数,算的少
将乘数拆分成2的幂的和,即 $min= a0*2^0 + a1*2^1 + a2*2^2 +…+ an*2^n $ 其中ai取值为0或1

举例:
1312 = 13(8+4) = 13*(2^3 + 2^2) = 13<<3+13<<2

进一步简化
如上示例,需要左移5次,存在重复计算,可以进行简化

tmp = 13<<2 = 52
ans = tmp
tmp = tmp<<1 = 104
ans = ans+tmp

简化为执行3次左移和1次加法

代码实现

def multiply(A: int, B: int) -> int:
    min_num = min(A, B)
    max_num = max(A, B)
    ans = 0
    while min_num:
        if min_num & 1:
            ans += max_num
        min_num >>= 1
        max_num <<= 1 # 或者 max_num+=max_num
    return ans


if __name__ == '__main__':
    print(multiply(1, 10))  # 10
    print(multiply(2, 10))  # 20
    print(multiply(3, 4))  # 12
    print(multiply(13, 12))  # 156

其他:python中的 bit_length() 方法
语法:int.bit_length()
说明:返回表示二进制整数的位数

print(7.bit_length()) # 3
print(-7.bit_length()) # 3
def multiply(A: int, B: int) -> int:
    min_num = min(A, B)
    max_num = max(A, B)
    ans = 0
    for i in range(min_num.bit_length()):
        if min_num & 1:
            ans += max_num
        min_num >>= 1
        max_num <<= 1
    return ans
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值