整数的二进制表达中有多少个1

整数的二进制表达中有多少个1

统计整型的二进制表达中1的个数

【题目】
给定一个整型(32位整数)n,可为0,可为正,也可为负,返回该整型二进制表达中1的个数


无符号右移

算法思路:每次无符号右移时统计整型n为1的次数

注意:python无符号运算,非负数有符号移位运算和无符号运算相同,因此需额外处理负数的无符号移位运算,将其转换为可以间接求的正数。
方法1:
∵ − n = ∼ n + 1 ∴ ∼ n = − n − 1 \because -n= \sim n+1 \\ \therefore \sim n= -n-1 n=n+1n=n1
负数1的个数可以转换为(其相反数-1)中0的个数, 即32 - (转换数中1的个数)

方法2:将负数转换为对应的无符号整数 n + 2 32 n+2^{32} n+232,在统一移位计算1的个数;


相应代码
# 负数转换为(相反数-1)中0的个数, 32 - (1的个数)
def count_one1(n):
    if n < -2 ** 31 or n > 2 ** 31 - 1:
        return
    if n == -2 ** 31:
        return 1
    elif n >= 0:
        count = 0
        while n != 0:
            count += n & 1
            n = n >> 1
        return count
    else:
        count = 0
        n = -n - 1
        while n != 0:
            count += n & 1
            n = n >> 1
        return 32 - count

# 统一转换成无符号数(0 ~ 2 ^ 32 - 1)
# 负数 + 2 ^ 32
def count_one2(n):
    if n < -2 ** 31 or n > 2 ** 31 - 1:
        return
    if n < 0:
        n += 2 ** 32
    count = 0
    while n != 0:
        count += n & 1
        n = n >> 1
    return count

按位与移除最右侧1

算法思路:
方法3n & (n - 1)移除最右侧的1,统计移除次数;
方法4n & (~n + 1)获取最右侧的1,原数相减获取最右侧的1即移除最右侧的1,统计移除次数;

注意
n & (n - 1)移除最右侧的1和n & (~n + 1)获取最右侧的1是位运算常用技巧。
实际上两者等价,简单证明如下。
n − ( n    &    ( n − 1 )   ) = n    &    ( 0 X f f f f f f f f − n + 1 ) = n    &    ( ∼ n + 1 ) n - (n \; \& \; (n - 1) \,) = n \; \& \; (0Xffffffff - n + 1) = n \; \& \; (\sim n + 1) n(n&(n1))=n&(0Xffffffffn+1)=n&(n+1)

相应代码
# n & (n - 1)逐个移除最右侧1,统计1的个数
def count_one3(n):
    if n < -2 ** 31 or n > 2 ** 31 - 1:
        return
    count = 0
    while n != 0 and n != -2 ** 31:
        n = n & (n - 1)  # 移除最右侧1
        count += 1
    if n == -2 ** 31:
        count += 1  # 统计符号位1
    return count

# n & (~n + 1)获取最右侧1,统计1的个数
def count_one4(n):
    if n < -2 ** 31 or n > 2 ** 31 - 1:
        return
    count = 0
    while n != 0 and n != -2 ** 31:
        n -= n & (~n + 1)  # 移除最右侧1
        count += 1
    if n == -2 ** 31:
        count += 1  # 统计符号位1
    return count

平行算法

算法思路:
四次$(n ; & ; c_i) +(,(n >> i , ) ; & ; c_i) $
登峰造极之操作,大家可略微了解,加强对位运算的理解。
以-1为例,将十六进制以二进制展开,帮助大家更好地理解其中的奥妙。
r 0      : 1111   1111   1111   1111   1111   1111   1111   111 1 ‾ c 1      : 0101   0101   0101   0101   0101   0101   0101   01 01 ‾ r 1      : 1010   1010   1010   1010   1010   1010   1010   10 10 ‾ c 2      : 0011   0011   0011   0011   0011   0011   0011   0011 ‾ r 2      : 0100   0100   0100   0100   0100   0100   0100   0 100 ‾ c 3      : 0000   1111   0000   1111   0000   1111   0000 ‾   1111 ‾ r 3      : 0000   1000   0000   1000   0000   1000   0000   1000 ‾ c 4      : 0000   0000   1111   1111   0000 ‾   0000 ‾   1111 ‾   1111 ‾ r 4      : 0001   0000   0001   0000   0001   0000   000 1 ‾   0000 ‾ c 5      : 0000 ‾   0000 ‾   0000 ‾   0000 ‾   1111 ‾   1111 ‾   1111 ‾   1111 ‾ r 5      : 0000   0000   0000   0000   0000   0000   00 10 ‾   0000 ‾ r_0 \; \;:1111 \, 1111 \, 1111 \, 1111 \, 1111 \, 1111 \, 1111 \, 111\underline{1} \\ c_1 \; \;: 0101 \, 0101 \, 0101 \, 0101 \, 0101 \, 0101 \, 0101 \, 01\underline{01} \\ r_1 \; \;: 1010 \, 1010 \, 1010 \, 1010 \, 1010 \, 1010 \, 1010 \, 10\underline{10} \\ c_2 \; \;: 0011 \, 0011 \, 0011 \, 0011 \, 0011 \, 0011 \, 0011 \, \underline{0011} \\ r_2 \; \;: 0100 \, 0100 \, 0100 \, 0100 \, 0100 \, 0100 \, 0100 \, 0\underline{100} \\ c_3 \; \;: 0000 \, 1111 \, 0000 \, 1111 \, 0000 \, 1111 \, \underline{\color{#0000FF}{0}000} \, \underline{\color{#0000FF}{1}111} \\ r_3 \; \;: 0000 \, 1000 \, 0000 \, 1000 \, 0000 \, 1000 \, 0000 \, \underline{1000} \\ c_4 \; \;: 0000 \, 0000 \, 1111 \, 1111 \, \underline{\color{#0000FF}{0000}} \, \underline{0000} \, \underline{\color{#0000FF}{1111}} \, \underline{1111} \\ r_4 \; \;: 0001 \, 0000 \, 0001 \, 0000 \, 0001 \, 0000 \, 000\underline{1} \, \underline{0000} \\ c_5 \; \;: \underline{\color{#0000FF}{0000}} \, \underline{\color{#0000FF}{0000}} \, \underline{\color{#0000FF}{000}0} \, \underline{0000} \, \underline{\color{#0000FF}{1111}} \, \underline{\color{#0000FF}{1111}} \, \underline{\color{#0000FF}{111}1} \, \underline{1111} \\ r_5 \; \;: 0000 \, 0000 \, 0000 \, 0000 \, 0000 \, 0000 \, 00\underline{10} \, \underline{0000} \\ r0:11111111111111111111111111111111c1:01010101010101010101010101010101r1:10101010101010101010101010101010c2:00110011001100110011001100110011r2:01000100010001000100010001000100c3:00001111000011110000111100001111r3:00001000000010000000100000001000c4:00000000111111110000000011111111r4:00010000000100000001000000010000c5:00000000000000001111111111111111r5:00000000000000000000000000100000
的确费了很大功夫,我来解释下运算的原理及过程。
r 0 r_0 r0为初始值,这里为-1。
$(n ; & ; c_i) +(,(n >> i , ) ; & ; c_i) $在错位统计 2 i 2^i 2i组1的个数,并合并累积到大组 r i r_i ri中。
c i c_i ci为第i次操作的常数项
  划横线的是本质上起作用的位置,再扩展至32位,即为整个 c i c_i ci
  蓝色部分的所在的位置表示可取0,可取1,常数项可以修改
r i r_i ri为第i次运算的结果值, r 5 r_5 r5为最终统计1的数量。

相应代码

最终以如图所示的常数进行运算,与左神书上一致。

# 平行算法
# 对负数有符号右移转换正数无符号右移处理
# 换常数进行测试
def count_one5(n):
    if n < -2 ** 31 or n > 2 ** 31 - 1:
        return
    if n < 0:
        n += 2 ** 32
    n = (n & 0x55555555) + ((n >> 1) & 0x55555555)
    n = (n & 0x33333333) + ((n >> 2) & 0x33333333)
    n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f)
    n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff)
    n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff)
    return n

测试代码

以无符号右移代码为例,进行测试,其他测试用例相同。

# 简单测试
if __name__ == '__main__':
    n = 0
    print(count_one1(n))  # 0
    n = 3
    print(count_one1(n))  # 2
    n = -1
    print(count_one1(n))  # 32
    n = -3
    print(count_one1(n))  # 31

小结

位运算考察整数进制的表示和理解,建议拿笔和纸带入用例推导一番,加强训练。

有任何疑问和建议,欢迎在评论区留言和指正!

感谢您所花费的时间与精力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值