您的位置:首页 > 职场人生

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

2016-04-25 15:40 459 查看
【前情提要】

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


运行结果为:



【右移】

但是,在程序中使用了除以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在前方,希望的路上。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: