今天开始看《编程之美》了,这是一本评价很高的书,找工作当然少不了它。
部分题目已经在书中给出了答案,这里只是给出我的一些想法,有些也来源于此书,就当作做笔记吧。最后,感谢作者为我们奉献了一本优秀的程序员找工作宝典。
题目介绍:给定一个字节的无符号整型变量,求其二进制表示中“1”的个数,要求算法的执行尽可能高效。
因为题目要求算法尽可能高效,因此,对于书中的解法一大家应该不会考虑。不仅思路简单,而且其中用到的运算也很耗时,比如求余和除法。
大家最应该想到的应该就是解法二了,虽然思路同解法一一样,但使用了更为高效的位运算。
这里要提一下C++中的bitset,假如要求34的二进制表示中“1”的个数,可以这样来求:
bitset<8> b(34);
cout << b.count() << endl;
上面的代码声明了一个8位的bitset变量b,然后调用b的count()函数,它返回的就是这个bitset中被置位的个数。
代码很简洁,但是,如果细看count()函数的代码,就知道,它采用的就是最简单的方式:
size_t
count() const _GLIBCXX_NOEXCEPT
{ return this->_M_do_count(); }
size_t
_M_do_count() const _GLIBCXX_NOEXCEPT
{
size_t __result = 0;
for (size_t __i = 0; __i < _Nw; __i++)
__result += __builtin_popcountl(_M_w[__i]);
return __result;
}
count()会调用_M_do_count(),_M_do_count()会依次检测它的所有位。因此,代码虽然简洁,但是效率并不高。
解法三的想法很好,前面的思路基本都是遍历这个整型变量的所有位,而解法三期望复杂度只与“1”的个数有关。
int Count(BYTE v)
{
int num = 0;
while(v) {
v &= v - 1;
++num;
}
return num;
}
v与v - 1进行与操作可以将v的最后一个“1”变成0,从而得到有1个“1”。这种方式还是很巧妙的。
至于解法四和解法五都采用了用空间换时间的策略,只不过解法五更加直接。
扩展问题:
1. 如果变量是32位的DWORD,你会使用上述的哪一个算法,或者改进哪一个算法?
解法一的时间复杂度是O(logN),也就是位数,而且使用了较慢的四则运算。
解法二的时间复杂度也是O(logN),但使用的是位运算,效率较第一种高。
解法三的时间复杂度是该数中“1”的个数,且使用位运算,效率更高。
解法四用的case分支语句,时间复杂度依赖于给定的数。
解法五很直接,时间复杂度为O(1)。
当位数较多时,肯定是解法三更好。如果位数较少的话,解法四和五也可以。这里是32位,用解法三。
2. 给定两个正整数A和B,把A变成B需要改变多少位?也就是说,整数A和B的二进制表示中有多少位是不同的?
将A和B异或,当对应的位不同时,结果为1,然后求结果中有多少个“1”就OK了。