Recursive
50 Pow(x, n)
这个题一开始我写成这样:
var myPow = function(x, n) { if (n < 0){ x = 1 / x n = -n } if (n === 1) return x; return x * myPow(x, n-1) };
看着好像没什么问题,但是当n很大的时候,比如1万多的时候,这样写就会报错:
Line 6: RangeError: Maximum call stack size exceeded
很明显,堆栈调用太深了。然后老师说要用分治来做,我写成这样就通过了:
// 64ms 84.16% 33.8mb 34.15% var myPow = function(x, n) { if (n === 0) return 1 if (n < 0){ x = 1 / x n = -n } if (n === 1) return x; if (n % 2 === 0) { let p = myPow(x, n/2) return p * p }else { let p = myPow(x, Math.floor(n/2)) return p * p * x } }; console.log(myPow(1.00001, 123456)) // 3.43684
大概思路就是:n个相乘的x分开两半来算,因为两半的结果是一样的,因此就只需要算一半的就可以了,以此类推,一半一半的分下去。
值得一提的是,老师给出的是python代码,我也按照他那样写两个return试着提交了下,内存消耗没有变,但是速度变成了80ms,击败了17.64%的用户。orz
if (n % 2 === 0) { return myPow(x*x, n/2) }else { return myPow(x*x, Math.floor(n/2)) * x }
非递归的做法
老师给了一个用位运算的非递归做法,然后给出了python代码。我用js写了一下:
var myPow = function(x, n){ if (n === 0) return 1; if (n < 0){ x = 1 / x n = -n; } let pow = 1 while (n>0){ if (n & 1){ pow *= x } x *= x n >>= 1 } return pow }
看起来没什么问题,但是最后卡在一个测试用例上:
myPow(2.00000, -2147483648)
期望输出是0,但是我这里会输出1。打了下日志发现,while循环走了一次就出来了。原因是,题目说:
n 是 32 位有符号整数,其数值范围是 [−2^31, 2^31 − 1]
-2147483648刚好就是下限−2^31,这里就涉及一些补码反码的知识了,看了一篇博客,感觉讲得还不错。
首先,n = -n;
的时候,虽然-2147483648变成了2147483648,但是移位的时候就会发现,2147483648 >> 1之后变成了-1073741824。然后while判断为false没有没有继续执行了。
那么为什么2147483648 >> 1 === -1073741824
呢?
ECMAScript 整数有两种类型,即有符号整数(允许用正数和负数)和无符号整数(只允许用正数)。在 ECMAScript 中,所有整数字面量默认都是有符号整数,这意味着什么呢?
有符号整数使用 31 位表示整数的数值,用第 32 位表示整数的符号,0 表示正数,1 表示负数。数值范围从 -2147483648 到 2147483647。
那对-2147483648取负值时,按理论应该是2147483648,但超过int能表达的最大正值,相当于2147283647+1=0111...1111+0000...0001=1000...0000=-2147483648(按补码理解)。也就是说对-2147483648取负仍然是-2147483648。
主要是因为2147483648,但超过int能表达的最大正值,用二进制表示的话就依然是1000...0000,那么移位运算的时候也会依照这个二进制数来计算,而负数的移位规则首先是保持符号不变,所以,1000...0000>>1就变成1100....0000,这个数就是-2^30=-1073741824 由此产生了bug。
那怎么办呢。我试着查了下,没什么好的结果。我就让在while里再判断一次负数好了,结果倒是通过了测试。但是和预想的不一样啊,这样写也没有更快。
完整代码:
// 72 ms 48.43% 34.3 MB 5.69% var myPow = function(x, n){ if (n === 0) return 1; if (n < 0){ x = 1 / x n = -n; } let pow = 1 while (n>0){ if (n & 1){ pow *= x } x *= x n >>= 1 if (n < 0){ n = -n } } return pow }
ps:老师写的python的代码并不会出现这个问题,python大法好啊