剑指Offer面试题10:二进制中1的个数

题目描述:请实现一个函数,输入一个整数,输入该数二进制表示中1的个数。
例如:把9表示成二进制是1001,有2位是1,所以输入9,该函数输出2

可能引起死循环的解法

思路:先判断整数二进制数的最后一位是不是1,然后整数右移一位,此时原来处于倒数第二位就被移到最右边了,再判断是否是1。也就是,依次右移,如果最后一位是1,则得到1,如果是0,则得到0
这种思路中,待判断的整数右移,而辅助判断的数1是不动的

int NumberOf1(int n){
    int count = 0;
    while(n != 0){
        if((n & 1) == 1)
            count++;
        n = n >> 1;
    }
    return count;
}

这么看起来上述代码挺正常,但是暗藏杀机,就是当n为负数的时候,无论移动多少次,最高位填1,最后n的所有位将变为1,进入死循环……
把整数右移一位和把整数除以2在数学上是等价的,那么上述代码中把右移运算换成除以2可以吗?
答案是否定的,因为除法的效率比移位运算要低得多,在实际编程中尽可能使用移位运算代替乘除法

常规解法

思路:既然整数移动可能进入死循环,那么换种思路,我们不移动待判断的整数,而是去左移辅助判断数1,那么移动1次,2次,3次,……,辅助判断数为10,100,1000,……,那么通过&运算我们可以判断整数的低x次位是否为1
经过32次移动后,辅助判断数变为0,作为循环终止的条件
这边有个小陷阱,就是n&flag的结果是多少证明该位为1呢?我们可以从flag下手,flag只有待判断的这一位是1,如果整数中这一位为1,则计算的结果等于flag;如果整数为0,则计算的结果就是0

int NumberOf1(int n){
    int flag = 1;
    int count = 1;
    while(flag != 0){   //32位的整数需要循环32次
        if((n & flag) == flag){
            count++;
        }
        flag = falg << 1;  //左移
    }
}

惊喜的解法

这种解法就不是那么常规的,而是很巧妙ԅ(¯㉨¯ԅ)
下面分析这种思路是怎么来的~~~
分析一个数减去1的情况。如果一个整数不等于0,那么该整数的二进制中至少有一位是1。
1.假设最后一位为1,那么减去1后,最后一位变为0,而其他所有位保持不变。也就是最后一位相当于进行了取反操作,由1变成了0。
2.最后一位不为1而是0的情况。如果该数的二进制表示中最右边1位于第m位,那么-1后,第m位由1变为0,第m位之后的所有0变为1,第m位之前的所有位保持不变。
eg:1100 -1后,第二位变为0,后面的两个0变为1,前面的1保持不变,所以得到结果1011
总结上述两种情况,我们发现一个数-1后,最右边的1变为0,如果右边还有0的话,都变为1,而它左边的数都保持不变!!!
接下来就是见证奇迹的时刻了!!!
把一个整数和它减去1的结果做&运算,相当于把它最右边的1变为0
eg:1100 & 1011 = 1000
基于这种思路,代码如下:

int NumberOf1(int n){
    int count = 0;
    while(n != 0){
        count++;
        n = n & (n-1);
    }
    return count;
}

相关题目:

1.用一条语句判断一个整数是不是2的整数次方。
分析:一个整数如果是2的整数次方,那么二进制表示中有且只有一位是1,而其他所有位都是0.根据前面的分析,我们将整数-1后再和它本身做&运算,那么这个整数中唯一的1就会变成0
(a & (a-1)) == 0 ? 是:不是
2.输入两个整数m和n,计算需要改变m的二进制表示中的多少位才能得到n
分析:首先求m^n,则计算的结果中有多少位为1,则表示m和n有多少位不同

举一反三:把一个整数减去1之后再和原来的整数做位与运算,得到的结果相当于把整数的二进制表示中的最右边一个1变为0!

你可能感兴趣的:(LeetCode)