一道Google面试题引发的代码优化战

【前情提要】
Coder在第一篇技术博里,以交换两个给定变量的值为例,谈到了代码的优化问题。今天我们以一道Google面试题为切入点,继续谈优化。这道题做得好,今夜我们都是Google-ese.

【Talk is cheap,show you the code】
Q:写一个函数,返回参数二进制中1的个数。
e.g.15 二进制 0000 1111 返回4个1
函数原型:
int count_one_bits(unsigned int value)
{
//返回1的位数
}

就以15为例,我们来分析这道题的算法:
如上,15的二进制序列中有4个1,程序最终结果应为4。该如何统计1的个数呢?试试这样的方法:从二进制数最低位开始判断,如果其为1,计数器加1,否则不计数。将二进制/2,继续判断,直到32位循环判断结束,打印计数输出。根据这个思路,我们可以写出以下程序:

#include <stdio.h>

int main()
{
    int num = 15;
    int count = 0;
    int i = 0;
    for (i = 0; i <= 32;i++)//while(num)
    {
        if (num % 2 == 1)
        {
            count++;
        }
        num = num / 2;
    }
    printf("%d\n", count);
    return 0;
}

运行结果为:

一道Google面试题引发的代码优化战_第1张图片

【右移】
但是,在程序中使用了除以2的方式将二进制位进行循环,很可能会产生溢出现象。为了避免溢出,我们可以使用右移运算符代替/2,功能是一样的,但效率更高,性能更优。
–右移小tip:
右移分逻辑一位位和算数移位。无符号数执行逻辑移位,右移一位,左边补0;有符号数执行算数移位,右移一位,左边补符号位(负数补1,正数补0)。注意:右移并没有改变原数的值,只是产生的结果移位。
所以将”num=num/2”改为:

num=num>>1;

【按位与】
上述程序在判断二进制位是否为1时,采用了模2取余的方式。我们也可以使用按位与的方式,将if语句改为这样:

if ((num&1)==1)

相似的灵魂总会相遇,这样写可以达到同样的判断效果,程序输出结果正确。

【最优呈现】
但是!一个很重要的问题浮出水面【重磅!!!Coder刚开始在做这道题的时候,只用了15做例子,得到正确的结果后没有继续深思,以致于忽略了一个很重要的问题:15是无符号数,但是Coder并没有考虑有符号数的情况。用-1来试,看看结果会怎样?
数据在计算机中以其二进制补码的形式存储,我们先得到-1的补码:
原码: 10000000 00000000 00000000 00000001
反码: 11111111 11111111 11111111 11111110(符号位不变,其余按位取反)
补码: 11111111 11111111 11111111 11111111(反码加1)
套用之前的程序,带入-1:

#include <stdio.h>

int main()
{
    int num = -1;
    int count = 0;
    int i = 0;
    for (i = 0; i <= 32;i++)//while(num)
    {
        if(num%2==1)
        {
            count++;
        }
        num = num>>1;
    }
    printf("%d\n", count);
    return 0;
}

得到结果为:

咦?结果不正确。我们再试试按位与的方式:

#include <stdio.h>

int main()
{
    int num = -1;
    int count = 0;
    int i = 0;
    for (i = 0; i <= 32;i++)//while(num)
    {
        if ((num&1)==1)
        {
            count++;
        }
        num = num>>1;
    }
    printf("%d\n", count);
    return 0;
}

得到结果为:

呀!同样不正确,看来上述方法只适用于无符号数。为了兼满足有符号数和无符号数,我们给出以下方法:

#include <stdio.h>

int main()
{
    int num = -1;
    int count = 0;
    while (num)
    {
        count++;
        num = num&(num - 1);
    }
    printf("%d\n", count);
    return 0;
}

观察while循环部分这行代码:

num = num&(num - 1);

和前面方法的不同点在于,它并会循环32次,而是二进制位中有几个1就循环几次,每次按位与都消掉最低位的1。
【举个栗子:
num=10; //1010
(num-1) //1001
num=num&(num-1); //1000
(num-1) //0111
num=num&(num-1); //0000
循环两次后程序停下。我们看一下这种方式实现的结果是否正确:

Bingo~给出150分~最优解诞生~
最后我们在用题目要求的方式写成函数就可以了~:

#include <stdio.h>

int count_one_bit(int num)
{
    int count = 0;
    while (num)
    {
        count++;
        num = num&(num - 1);
    }
    return count;
}

int main()
{
    int num = -1;
    int ret = count_one_bit();
    printf("%d\n", ret);
    return 0;
}

【结语】
有趣是第一生产力,严谨是第二生产力,求好是第三生产力。
勉励各位Coder,写更优的代码,做专业的程序员,Google在前方,希望的路上。

你可能感兴趣的:(面试题,谷歌,代码优化)