剑指Offer-二进制中的1的个数
题目
输入一个整数,输出该数二进制表示中1的个数。
必备知识-原码、反码与补码
原码
将最高位作为符号位(以0代表正,1代表负),其余各位代表数值本身的绝对值(以二进制表示)。
如果是8位二进制,则:
+1 的原码为:0000 0001
-1 的原码为:1000 0001
反码
- 正数的反码与原码相同。
- 负数的反码是在其原码的基础上,符号位保持不变,其余各位取反。
如果是8位二进制,则:
+1 的反码为:0000 0001
-1 的反码为:1111 1110
补码
- 正数的补码与原码相同。
- 负数的补码是在其原码的基础上,符号位保持不变,其余各位取反,最后再 +1。
如果是8位二进制,则:
+1 的补码为:0000 0001
-1 的补码为:1111 1111
总结
-
正数的原码、反码与补码是完全相同的,而负数的原码、反码与补码是完全不同的。
-
反码的出现解决了“正负数相加等于 0”的问题。
-
补码的出现解决了“两个零 +0 与 -0 存在”的问题,而且可以将符号位和数值位统一处理,加法与减法也可以统一处理。
解法探析
解法1
首先想到的是将二进制数不断右移,这样的话,该数的每一位都会依次移到最右边,每右移一次就将该数和 1 做位与运算,来计算 1 的个数。(由于 1 最右边的一位是 1,其他位都是 0,如果一个整数与 1 做与运算后,结果是 1,就表示该数最右边一位是 1,否则就是 0)
int NumberOf1(int num)
{
int count = 0;
while (num != 0) {
if ((num & 1) == 1) {
count++;
}
num >>= 1;
}
return count;
}
上面这种方法没有考虑负数的情况,和正数右移最高位补0不同,负数右移最高位会补1,这样整个数字会变成全1,导致死循环。
解法2
既然右移行不通,会导致死循环,那么可以反过来,将1不断左移,然后和目标数做与运算来求 1 的个数。
int NumberOf1(int num)
{
int count = 0;
unsigned int flag = 1;
while (flag != 0) {
if ((num & flag) != 0) {
count++;
}
flag <<= 1;
}
return count;
}
如计算正数 145 的二进制中 1 的个数:
1001 0001 目标数
0000 0001 1与目标数做与运算得到的最低位是否为1
0000 0010 1左移1位,判断其与目标数做与运算得到的次低位是否为1
0000 0100 1左移2位,判断其与目标数做与运算得到的次次低位是否为1
......
解法3
解法2的时间复杂度为O(num的位数),num有多少位就要循环多少次。可以利用一个小技巧,降低算法的时间复杂度。
把一个整数减去 1 之后再和原来的整数做位与运算,得到的结果相当于把整数的二进制表示中最右边的 1 变成 0。
那么一个整数的二进制表示中有多少个 1,就是可以进行多少次这样的操作。
int NumberOf1(int num)
{
int count = 0;
while (num != 0) {
++count;
num = (num - 1) & num;
}
return count;
}
个人主页: