一道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位循环判断结束,打印计数输出。根据这个思路,我们可以写出以下程序:
运行结果为:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/13/b0af626d4f961e72483c1d6d8875c9c4)
【右移】
但是,在程序中使用了除以2的方式将二进制位进行循环,很可能会产生溢出现象。为了避免溢出,我们可以使用右移运算符代替/2,功能是一样的,但效率更高,性能更优。
–右移小tip:
右移分逻辑一位位和算数移位。无符号数执行逻辑移位,右移一位,左边补0;有符号数执行算数移位,右移一位,左边补符号位(负数补1,正数补0)。
所以将”num=num/2”改为:
【按位与】
上述程序在判断二进制位是否为1时,采用了模2取余的方式。我们也可以使用按位与的方式,将if语句改为这样:
相似的灵魂总会相遇,这样写可以达到同样的判断效果,程序输出结果正确。
【最优呈现】
但是!一个很重要的问题浮出水面【重磅!!!Coder刚开始在做这道题的时候,只用了15做例子,得到正确的结果后没有继续深思,以致于忽略了一个很重要的问题:15是无符号数,但是Coder并没有考虑有符号数的情况。用-1来试,看看结果会怎样?
数据在计算机中以其二进制补码的形式存储,我们先得到-1的补码:
原码: 10000000 00000000 00000000 00000001
反码: 11111111 11111111 11111111 11111110(符号位不变,其余按位取反)
补码: 11111111 11111111 11111111 11111111(反码加1)
套用之前的程序,带入-1:
得到结果为:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/13/61a13934b2eab9876727acc2549cf0a3)
咦?结果不正确。我们再试试按位与的方式:
得到结果为:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/13/ff9f9edc431120d1f8fa5ae85e2fa8b1)
呀!同样不正确,看来上述方法只适用于无符号数。为了兼满足有符号数和无符号数,我们给出以下方法:
观察while循环部分这行代码:
和前面方法的不同点在于,它并会循环32次,而是二进制位中有几个1就循环几次,每次按位与都消掉最低位的1。
【举个栗子:
num=10; //1010
(num-1) //1001
num=num&(num-1); //1000
(num-1) //0111
num=num&(num-1); //0000
循环两次后程序停下。我们看一下这种方式实现的结果是否正确:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/13/05cd403fa4f8911cfc3706efee8c9ce9)
Bingo~给出150分~最优解诞生~
最后我们在用题目要求的方式写成函数就可以了~:
【结语】
有趣是第一生产力,严谨是第二生产力,求好是第三生产力。
勉励各位Coder,写更优的代码,做专业的程序员,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; }
运行结果为:
【右移】
但是,在程序中使用了除以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在前方,希望的路上。
相关文章推荐
- Android面试题整理【转载】
- 架构师最怕程序员知道的10件事
- IT行业技术及程序员相关网站荟萃
- 71、月薪3万的程序员都避开了哪些坑
- 加班那点事(三)
- 加班那点事(二)
- 加班那点事(一)
- 面试中的排序算法总结
- 码农之无尽的任务:一个String带来的无底深渊
- 作为一个程序员我最大的遗憾
- JAVA程序员养成计划之JVM学习笔记(3)-JVM性能监控
- 前端程序员:月薪 5K 到 5 万,我干了啥(转)
- 面试题18:树的子结构
- 面试题13:在O(1)时间删除单链表结点
- 成为顶尖自由职业者必备的七个软技能之四:如何成为销售之王(转)
- 技术类应届生面试技巧(牛客网)
- 这些情况,去面试就是浪费时间!
- 最近一个刚刚毕业的朋友说,他面试时候,遇到最频繁的css问题就是垂直居中,这里给出几种垂直居中方式!
- Java常见面试题_2016
- 最新10大高薪行业出炉!你大学选对专业了么?