从反转二进制位中的位运算分治到不同语言的移位操作

反转二进制位其实是一个很简单的问题,比如说反转一个32位无符号整数的二进制位,从 0101 变成 1010(注意这里是无符号的)。

这个问题本身的复杂度是很低的,即使是用最直觉的算法,也无非是 O ( k ) O(k) O(k),也就是数字的位数,一位一位地反转。我随便写了一个:

class Solution:
    def reverseBits(self, n: int) -> int:
        res = 0
        for i in range(32):
            res *= 2
            res += n % 2
            n //= 2
        return res

当然这里用了一点秦九韶算法的意思,不过也并不难想。

后来了解到这个题有一种使用分治思想的做法,其实就是对这么一个直觉的具体化:如果把数字分成一堆组,然后每一组都反转,那整个数字就反转了;也就是旋转数组那个意思,反转前k位,然后反转后n-k位,然后再整体反转一下,字符串就反过来了。这个过程在纸上比划一下就很容易理解、

放到这个题也是如此。如果自顶向下,就是反转16个、8个、4个、2个,以此类推;如果自底向上,就是反转2个、4个、8个、16个,以此类推。写法也并不复杂,自顶向下是很简单的:

class Solution:
    def reverseBits(self, n: int) -> int:
        n = n << 16 | n >> 16
        n = (n & 0x00ff00ff) << 8 | (n & 0xff00ff00) >> 8
        n = (n & 0x0f0f0f0f) << 4 | (n & 0xf0f0f0f0) >> 4
        n = (n & 0x33333333) << 2 | (n & 0xcccccccc) >> 2
        n = (n & 0x55555555) << 1 | (n & 0xaaaaaaaa) >> 1
        return n

第一步之所以不用掩码,是因为反正只考虑16位(总共32位)。如果自底向上呢?很容易就能写出这样的代码:

class Solution:
    def reverseBits(self, n: int) -> int:
        n = (n & 0x55555555) << 1 | (n & 0xaaaaaaaa) >> 1
        n = (n & 0x33333333) << 2 | (n & 0xcccccccc) >> 2
        n = (n & 0x0f0f0f0f) << 4 | (n & 0xf0f0f0f0) >> 4
        n = (n & 0x00ff00ff) << 8 | (n & 0xff00ff00) >> 8
        n = n << 16 | n >> 16
        return n

但是这个写法在Python里是错误的。我试了一下其他语言,其他语言都是对的(换成对应的逻辑右移运算符)。在C++、Java中的正确性容易理解,因为他们有明确的int类型,超出后会溢出;但为什么在JavaScript中也是正确的,JS不是和Python一样没有明确的32位整数类型吗?因为JavaScript的位运算符只会作用在整数的后32位上,这也是一个非常著名的不符合直觉的地方。

回过头来看,这段代码之所以在Python里是错误的,是因为Python的位运算不会溢出,加上没有32位的限制,移位后会把数字变大,就超出了32位。正确的写法应该是这样:

class Solution:
    def reverseBits(self, n: int) -> int:
        n = (n & 0x55555555) << 1 | (n & 0xaaaaaaaa) >> 1
        n = (n & 0x33333333) << 2 | (n & 0xcccccccc) >> 2
        n = (n & 0x0f0f0f0f) << 4 | (n & 0xf0f0f0f0) >> 4
        n = (n & 0x00ff00ff) << 8 | (n & 0xff00ff00) >> 8
        n = (n & 0x0000ffff) << 16 | (n & 0x0000ffff) >> 16
        return n

通过掩码把范围限制在32位的范围内,才能获得正确结果。

再回头看看自顶向下的解法。之所以不用掩码,一方面是只考虑16位,不需要掩码;另一方面也是后面的掩码会帮助他缩小范围,不再需要在第一步就限制在32位的范围内了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值