剑指Offer编程题-二进制中1的个数

题目:二进制中1的个数

题目描述:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

解题思路

预备知识:

  • 一个int型的数占用4个字节,即32位。那么正整数1的二进制表示为0000 0000 0000 0000 0000 0000 0000 0001,补足32位。最高位为符号位(0表示正数,1表示负数)
  • 负数在计算机中以原码的补码形式表示
  • 原码:正数的原码是它的二进制表示;负数的原码是它的绝对值取二进制后,最高位补1
  • 反码:正数的反码与原码相同;负数的反码等于它的原码除符号位外按位取反
  • 补码:正数的补码和原码相同;负数的补码等于它的反码在最后一位加1
  • 负数补码简要求法:绝对值的原码按位取反后加1

求解:

  • 方法一:假设一个数为0000 .... 0101,1的二进制为0000 .... 0001,这个数和1相与后,除最后一位外,所有位都变成0,最后一位如果是1,相与后的结果为1,如果是0,相与后的结果为0,这样就可以统计最后一位是否为1。然后将数右移1位,正数的话最高位会补0,原数变成0000 .... 0010,继续和1相与,统计下一位是否为1。当数变成0时,循环结束,统计出1的个数。
  • 存在的问题:对于负数,每次右移,最高位补1而不是0,一直右移会陷入死循环。解决方法有两个:a.如果一个int型数据占32位,那么循环的次数可以限制为32次;b.可以将负数转成正数再统计,比如-2的二进制表示为1111 .... 1110,要转正数需要将符号位变成0,可以将它与上0111 .... 11112^31-1,原数就变为0111 .... 1110,对应正数为2147483646,可直接用于移位统计,需要注意此时符号位少了1,最后的统计数应该再加上1。
  • 方法二:假设一个数n0000 .... 1010,它减1后的值(n-1)0000 .... 1001,可以看到,在减1后,原数二进制中倒数第一个1和它往后的数都被按位取反了,然后将两数相与n&(n-1)可以消除n中倒数第一个1,重复计算直至n为0就可以统计出1的个数。对于负数,同样会陷入死循环,因为负数最高位减1的时候,会借1补足符号位,永远不会变成0,解决方法可以采用上述将负数变成正数的方法。
  • 方法三:用Python的话可以直接用内置函数bin()返回二进制数然后统计1的个数

方法二的循环次数只与1的个数有关,而方法一的循环次数相当于位数最高的1的位数,所有方法二应该更优。

实现

python

方法1:

class Solution:
    def NumberOf1(self, n):
        # write code here
        a = 0
        if n<0:
            n = n&(2**31-1)
            a = 1
        while n:
            a += n&1
            n >>= 1
        return a

python

方法2:

class Solution:
    def NumberOf1(self, n):
        # write code here
        a = 0  
        if n<0:
            n = n&(2**31-1)
            a = 1
        while n:
            a += 1
            n = n & (n-1)
        return a

python

方法3:

class Solution:
    def NumberOf1(self, n):
        # write code here
        return bin(n).count("1") if n>=0 else bin(2**32+n).count("1")