剑指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;
}

个人主页:

www.codeapes.cn

posted @ 2020-01-29 11:04  Codeapes  阅读(200)  评论(0编辑  收藏  举报