别人家的面试题:一个整数是否是“4”的N次幂俄罗斯贵宾会
别人家的面试题:一个整数是否是“4”的N次幂
本文作者: 伯乐在线 -
十年踪迹
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。
这是 leetcode.com 的第二篇。与上一篇一样,我们讨论一道相对简单的问题,因为学习总讲究循序渐进。而且,就算是简单的问题,追求算法的极致的话,其中也是有大学问的。
别人家的面试题:统计“1”的个数
2016/05/27 · JavaScript · 5 评论 · Javascript, 算法
本文作者: 伯乐在线 -
十年踪迹
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。
小胡子哥 @Barret李靖 给我推荐了一个写算法刷题的地方 leetcode.com,没有 ACM 那么难,但题目很有趣。而且据说这些题目都来源于一些公司的面试题。好吧,解解别人公司的面试题其实很好玩,既能整理思路锻炼能力,又不用担心漏题 ╮(╯▽╰)╭。
长话短说,让我们来看一道题:
“4”的整数次幂
给定一个32位有符号整数(32 bit signed integer),写一个函数,检查这个整数是否是“4”的N次幂,这里的N是非负整数。
例如:
- 给定 num = 16,返回 true,因为 16 = 42
- 给定 num = 5,返回 flase
附加条件: 你能够不用循环和递归吗?
统计“1”的个数
给定一个非负整数 num,对于任意 i,0 ≤ i ≤ num,计算 i 的值对应的二进制数中 “1” 的个数,将这些结果返回为一个数组。
例如:
当 num = 5 时,返回值为 [0,1,1,2,1,2]。
/** * @param {number} num * @return {number[]} */ var countBits = function(num) { //在此处实现代码 };
1
2
3
4
5
6
7
|
/**
* @param {number} num
* @return {number[]}
*/
var countBits = function(num) {
//在此处实现代码
};
|
解题思路
如果忽略“附加条件”,这题还挺简单的对吧?简直是信手拈来:
JavaScript
function isPowerOfFour(num){ while(!(num % 4)){ num /= 4; } return num === 1; }
1
2
3
4
5
6
|
function isPowerOfFour(num){
while(!(num % 4)){
num /= 4;
}
return num === 1;
}
|
版本1 好像很简单、很强大的样子,它的时间复杂度是 log4N。有同学说,还可以做一些微小的改动:
JavaScript
function isPowerOfFour(num){ while(!(num % 4)){ num >>>= 2; } return num === 1; }
1
2
3
4
5
6
|
function isPowerOfFour(num){
while(!(num % 4)){
num >>>= 2;
}
return num === 1;
}
|
上面的代码用位移替代除法,在其他语言中更快,鉴于 JS 通常情况下非常坑的位运算操作,不一定速度能变快。
好了,最关键的是,不管是 版本1 还是 版本1.1 似乎都不满足我们前面提到的“附加条件”,即不使用循环和递归,或者说,我们需要寻找 O(1) 的解法。
按照惯例,大家先思考10秒钟,然后往下看 ——
解题思路
这道题咋一看还挺简单的,无非是:
- 实现一个方法
countBit
,对任意非负整数 n,计算它的二进制数中“1”的个数 - 循环 i 从 0 到 num,求
countBit(i)
,将值放在数组中返回。
JavaScript中,计算 countBit
可以取巧:
function countBit(n){ return n.toString(2).replace(/0/g,"").length; }
1
2
3
|
function countBit(n){
return n.toString(2).replace(/0/g,"").length;
}
|
上面的代码里,我们直接对 n 用 toString(2) 转成二进制表示的字符串,然后去掉其中的0,剩下的就是“1”的个数。
然后,我们写一下完整的程序:
function countBit(n){ return n.toString(2).replace(/0/g,'').length; } function countBits(nums){ var ret = []; for(var i = 0; i <= nums; i++){ ret.push(countBit(i)); } return ret; }
1
2
3
4
5
6
7
8
9
10
11
|
function countBit(n){
return n.toString(2).replace(/0/g,'').length;
}
function countBits(nums){
var ret = [];
for(var i = 0; i <= nums; i++){
ret.push(countBit(i));
}
return ret;
}
|
上面这种写法十分讨巧,好处是 countBit
利用 JavaScript
语言特性实现得十分简洁,坏处是如果将来要将它改写成其他语言的版本,就有可能懵B了,它不是很通用,而且它的性能还取决于
Number.prototype.toString(2) 和 String.prototype.replace 的实现。
所以为了追求更好的写法,我们有必要考虑一下 countBit
的通用实现法。
我们说,求一个整数的二进制表示中 “1” 的个数,最普通的当然是一个 O(logN) 的方法:
function countBit(n){ var ret = 0; while(n > 0){ ret += n & 1; n >>= 1; } return ret; }
1
2
3
4
5
6
7
8
|
function countBit(n){
var ret = 0;
while(n > 0){
ret += n & 1;
n >>= 1;
}
return ret;
}
|
俄罗斯贵宾会,所以我们有了版本2
这么实现也很简洁不是吗?但是这么实现是否最优?建议此处思考10秒钟再往下看。
不用循环和递归
其实这道题真心有好多种思路,计算指数之类的对数学系学霸们完全不是问题嘛:
JavaScript
const log4 = Math.log(4); function isPowerOfFour(num){ var n = Math.log(num) / log4; return n === (0|n); }
1
2
3
4
5
|
const log4 = Math.log(4);
function isPowerOfFour(num){
var n = Math.log(num) / log4;
return n === (0|n);
}
|
嗯,通过对数公式 logm(n) = log(n) / log(m) 求出指数,然后判断指数是不是一个整数,这样就可以不用循环和递归解决问题。而且,还要注意细节,可以将 log4 当做常量抽取出来,这样不用每次都重复计算,果然是学霸范儿。
不过呢,利用 Math.log 方法也算是某种意义上的犯规吧,有没有不用数学函数,用原生方法来解决呢?
当然有了!而且还不止一种,大家可以继续想30秒,要至少想出一种哦 ——
更快的 countBit
上一个版本的 countBit
的时间复杂度已经是 O(logN)
了,难道还可以更快吗?当然是可以的,我们不需要去判断每一位是不是“1”,也能知道
n 的二进制中有几个“1”。
有一个诀窍,是基于以下一个定律:
- 对于任意 n, n ≥ 1,有如下等式成立:
countBit(n & (n - 1)) === countBit(n) - 1
1
|
countBit(n & (n - 1)) === countBit(n) - 1
|
这个很容易理解,大家只要想一下,对于任意 n,n – 1 的二进制数表示正好是 n 的二进制数的最末一个“1”退位,因此 n & n – 1 正好将 n 的最末一位“1”消去,例如:
- 6 的二进制数是 110, 5 = 6 – 1 的二进制数是 101,
6 & 5
的二进制数是110 & 101 == 100
- 88 的二进制数是 1011000,87 = 88 – 1 的二进制数是
1010111,
88 & 87 的二进制数是 1011000 & 1010111 == 1010000
于是,我们有了一个更快的算法:
function countBit(n){ var ret = 0; while(n > 0){ ret++; n &= n - 1; } return ret; } function countBits(nums){ var ret = []; for(var i = 0; i <= nums; i++){ ret.push(countBit(i)); } return ret; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
function countBit(n){
var ret = 0;
while(n > 0){
ret++;
n &= n - 1;
}
return ret;
}
function countBits(nums){
var ret = [];
for(var i = 0; i <= nums; i++){
ret.push(countBit(i));
}
return ret;
}
|
上面的 countBit(88)
只循环 3 次,而“版本2”的 countBit(88)
却需要循环
7 次。
优化到了这个程度,是不是一切都结束了呢?从算法上来说似乎已经是极致了?真的吗?再给大家 30 秒时间思考一下,然后再往下看。
本文由俄罗斯贵宾会发布于Web前端,转载请注明出处:别人家的面试题:一个整数是否是“4”的N次幂俄罗斯贵宾会