面试题 10,二进制中1的个数(取整数中某一位的经典方法)

这道题我原本的思路是,对于输入的数字InputValue,每次模除2,如果为1,计数器加1,将InputValue右移1位,重复直到InputValue变为0。

这样做的弱点在于:

(1) 如果InputValue是负数,其采用的是反码表示,比如11111111 11111111 11111111 11111101表示的是-2(见负数的存储方式相关资料,http://book.51cto.com/art/201102/245131.htm)。这个时候将这个负数右移一位,为了保证右移后仍然是负数,其最高位上仍然为1(见上面那个括号中的参考资料),因此移如果一直右移一个负数InputValue,最后的结果是11111111 11111111 11111111 11111111,陷入死循环。而且题目里面明确说:“输出该数二进制表示中1的个数”,因此,需要将负数的二进制表示考虑在内。

(2) %2的效率很低,如果想取最低位,有更高效的方法:"&1"

更改后的思路如下,我们不再右移InputValue,而是每次将1左移一位,这样可以挨个取InputValue的每一位。

代码如下:

int OutputAmountofOne(const int inputValue){
    insigned int flag = 1;
    int count = 0;
    while(flag){
        if(inputValue & flag)
            count++;
        flag = flag << 1;
    }
    return count;
}

关于flag是定义为int 还是 unsigned int格式,经过实验和资料查找。发现两种格式下结果一致。

个人分析,原因在于,int和unsigned int在内存存储上一样,只是输出是会根据其类型而改变。同样的11111111 11111111 11111111 11111111,在int被解释为-1,在unsigned int就被认为是2^32-1。而在这段代码中,我们只在二进制上左移flag,并不涉及输出。因此结果一样。

这个方法的时间复杂度为O(N),N为InputValue的位数。

 

而实际上,有O(logN)的解法,也就是说,不用遍历InputValue的每一位,就可以统计出1的个数。

思路:例如,对于二进制数1000,若减1,则变为0111,就是说,对于二进制的减1,就是从末尾开始寻找1,寻找的过程中,凡是0位都变成1。直到寻找到第一个1,把它变为0,则这次减1运算完毕。

对于数1100,对其减1,变为1011,将此结果和原数1100做&操作,得到1000,我们发现末尾的第一个1就没有了。重复这个动作,直到结果变成0,这个减1动作的重复次数,就是1的个数。

代码:

int OutputAmountofOneLogN(const int inputValue){
    int value = inputValue;
    int count = 0;
    while(value){
        count++;
        int temp = value - 1;
        value = temp & value;
    }
    return count;
}

 

posted on 2013-11-29 17:47  Felix Fang  阅读(2285)  评论(0)    收藏  举报

导航