题目:请实现一个函数,输入一个整数,输出该数二进制表示中 二进制中1的个数 - 图1 的个数。例如,把 二进制中1的个数 - 图2 表示成二进制是 二进制中1的个数 - 图3,有 二进制中1的个数 - 图4 位是 二进制中1的个数 - 图5。因此,如果输入 二进制中1的个数 - 图6,则该函数输出 二进制中1的个数 - 图7

    我们通过将数字与 1 相与,即可知道数字的二进制的最后一位是否为 1,我们只要逐渐的将数字进行右移,即可统计出数字中 1 的数目,所以会写下这样的代码

    1. public static int numbersOfOne(int n) {
    2. int count = 0;
    3. while (n != 0) {
    4. if ((n & 1) != 0) {
    5. count++;
    6. }
    7. n = n >> 1;
    8. }
    9. return count;
    10. }

    但是上面的代码有一个问题,那就是如果输入的整数为负数呢? 根据负数的表示原则,它的首位是 1 来表示数字的正负性,在右移的过程中,首位会进行补 1 以表示它的符号还是负的,这个时候统计出的数字就是错误的了,并且会造成上面代码的死循环,如下以 8 位表示的 -7 为例

    二进制中1的个数 - 图8
    为了解决负数引起的问题,我们可以反其道而行之,让 1 进行左移,然后与数字相与,同样也可以统计出数字 1 的个数,所以修改代码如下

    1. public static int numbersOfOne(int n) {
    2. int count = 0;
    3. int flag = 1;
    4. while (flag != 0) {
    5. if ((n & flag) != 0) {
    6. count++;
    7. }
    8. flag = flag << 1;
    9. }
    10. return count;
    11. }

    但是我们发现统计负数中 1 的个数的结果与我们想象的不符,比如我们统计 -7 的个数,结果为

    1. public static void main(String[] args) {
    2. System.out.println(numbersOfOne(-7)); // 30
    3. }

    结果是 二进制中1的个数 - 图9 而不是 ,难道是我们的算法错了,其实不是 二进制中1的个数 - 图10,而是计算机存储数字是以其补码进行存储的,对于正数来说,它的补码是它本身,而对于负数,则是将除符号位进行取反,然后加 二进制中1的个数 - 图11,所以 的补码为 二进制中1的个数 - 图12

    二进制中1的个数 - 图13
    上面计算出一个数字二进制表示的 二进制中1的个数 - 图14 的个数需要进行 二进制中1的个数 - 图15 次循环(对于 int 类型的数据),我们可以进一步对算法进行改进,使得二进制中有几个 二进制中1的个数 - 图16 则进行几次循环。为了理解下面讲的算法,我们来看一下将一个数字减去 二进制中1的个数 - 图17 它的二进制会发生什么变化?

    先说结论,假设数字二进制最右边的 二进制中1的个数 - 图18 在第 二进制中1的个数 - 图19 位,即第 二进制中1的个数 - 图20 位后面的位全是 二进制中1的个数 - 图21,在减去 二进制中1的个数 - 图22 之后,第 二进制中1的个数 - 图23 位的 二进制中1的个数 - 图24 变成 二进制中1的个数 - 图25,后面的 二进制中1的个数 - 图26 全部变为 二进制中1的个数 - 图27,而第 二进制中1的个数 - 图28 位前面的数字保持不变,以 二进制中1的个数 - 图29 位表示的 二进制中1的个数 - 图30 为例

    二进制中1的个数 - 图31
    那么我们将数字 二进制中1的个数 - 图32二进制中1的个数 - 图33 进行相与会得到什么,因为 二进制中1的个数 - 图34二进制中1的个数 - 图35 位前面的数字不变,所以 二进制中1的个数 - 图36 的第 二进制中1的个数 - 图37 位以前的数字与 二进制中1的个数 - 图38 相同,所以 n & (n - 1) 得到的第 二进制中1的个数 - 图39 位以前的二进制是不变的,而 二进制中1的个数 - 图40二进制中1的个数 - 图41 位变成 二进制中1的个数 - 图42,以及第 二进制中1的个数 - 图43 位以后的 二进制中1的个数 - 图44 变为 二进制中1的个数 - 图45,所以 n & (n - 1) 得到的第 二进制中1的个数 - 图46 位及第 二进制中1的个数 - 图47 位以后的二进制是 二进制中1的个数 - 图48,所以 n & (n - 1) 的效果就是将数字 二进制中1的个数 - 图49 中最右边的 二进制中1的个数 - 图50 给去除了

    二进制中1的个数 - 图51
    所以我们就可以写出这样的代码

    1. public static int numbersOfOne(int n) {
    2. int count = 0;
    3. while(n != 0) {
    4. count++;
    5. n = (n - 1) & n;
    6. }
    7. return count;
    8. }

    每次循环会去除一个 二进制中1的个数 - 图52,所以二进制中有几个 二进制中1的个数 - 图53,便会进行几次循环。

    扩展:

    • 用一条语句判断一个整数是不是 二进制中1的个数 - 图54 的整数次方。一个整数如果是 二进制中1的个数 - 图55 的整数次方,那么它的二进制表示中有且只有一位是 二进制中1的个数 - 图56,而其他所有位都是 二进制中1的个数 - 图57。所以我们统计该数二进制中 二进制中1的个数 - 图58 的个数,如果是一个,那么就是 二进制中1的个数 - 图59 的整数次方。
    • 输入两个整数 二进制中1的个数 - 图60二进制中1的个数 - 图61,计算需要改变 二进制中1的个数 - 图62 的二进制表示中的多少位才能得到 二进制中1的个数 - 图63。比如 二进制中1的个数 - 图64 的二进制表示为 二进制中1的个数 - 图65二进制中1的个数 - 图66 的二进制表示为 二进制中1的个数 - 图67,需要改变 二进制中1的个数 - 图68 中的 二进制中1的个数 - 图69 位才能得到 二进制中1的个数 - 图70。我们可以分两步解决这个问题,首先将这两个数进行异或,这样 二进制中1的个数 - 图71二进制中1的个数 - 图72 二进制不同的位会得到 二进制中1的个数 - 图73,相同的位是 二进制中1的个数 - 图74,我们只要统计异或后二进制中 二进制中1的个数 - 图75 的个数,即可得到需要改变的位数。