剑指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 .... 1111即2^31-1,原数就变为0111 .... 1110,对应正数为2147483646,可直接用于移位统计,需要注意此时符号位少了1,最后的统计数应该再加上1。 - 方法二:假设一个数
n为0000 .... 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")