面试官 : 请用代码实现 x^n
我: 面试官我想确认一下 , x 和 n 的范围 , 因为数据类型表示数是有极限的 , 比如在 javaScript 中,如果 x,n 太大我可能要使用 bigint 来保证结果不溢出 , 甚至超过 bigint 的范围,我可能要使用字符串来计算并表示最终结果 , 所以我想请问面试官 ,对 x ,n 有没有具体的范围 ?
面试官 : 好的 , 数据保证在合理范围内 ,无需担心溢出问题 , 主要写你的思路 。
我(心理活动) : 有点小紧张 ! 先来个稳妥的 , 憋着大招后面放🤡
撸码中 ...
我 : 面试官请看下面代码
function f(x,n){
ans = 1;
for(let i=0;i<n;i++){
ans *= x;
}
return ans;
}
这是我采用迭代的方法 , 对 x 进行累乘 。
面试官 : 还有其他写法吗 ?
我(窃喜) : 好的面试官 , 我将为您带来递归的写法。
function g(x,n){
if(n === 0) return 1;
return x*g(x,n-1);
}
面试官 : 为什么这道题可以使用递归 ?
我 : 好的 , 面试官 。 (内心 : 请看我表演)
因为所谓递归 ,在我看来就是递推 + 回溯的过程 ,
所谓递推 , 在这道题中 , 我是这样递推的 : 要求 x^n 就必须知道 x^n-1
, 同理 , 要求 x^n-1 必须知道 x^n-2 , 所以我们可以一直递推下去 :
一直递推到 x^0 , 到这里递推完成 ,接下来就是回溯了 。
所谓回溯 , 就是沿着之前走过的路回去 , 所谓来路便是去路 , 回溯也遵循着这样的佛法 ,
递推的终点 , 我们知道是 x^0 = 1, 既然知道了x^0是终点 , 那么沿着原路返回 , 就会遇到 :
要求 x^1 (只需在 x^0 上乘 x),
再遇到 x^2 (只需在 x^1 上乘 x) ,
最后依此类推
我发现 , 递推是不断地分解问题为小的问题 ,直到最小可解决 , 而回溯是沿着来路解决问题 。
所以要判断一个题目是否可以使用递归 , 我们首先就要判断以下几点
- 题目是否具有递归性 , 是否可递推? 是否可回溯 ?
- 递推是否有终点 , 题目中的终点就是 , n === 0 的时候
面试官(内心 : 小伙子不错) : 你有时间复杂度更优的解法吗 ?
我 : 面试官有的 , 我还想到使用快速幂的方法 。我先说一下 ,我大体的思路 :
在我刚刚的代码中 , 时间复杂度是 O(n) , 所以为了优化 , 我应该想一个时间复杂度为 O(log(n)) 或者 O(根号 n) 的算法 , 而快速幂就是一种 O(log(n))的算法 , 我有两种实现方法 :
- 在刚刚递归代码上采用指数折半的思想实现快速幂
- 使用二进制的思路实现快速幂
为了与刚刚的递归的代码 , 产生联系 ,我先展示第一种写法 :
function h(x,n){
if(n==0) return 1;
t = h(x,Math.floor(n/2));
if(n%2==0) return t*t;
else return t*t*x;
}
我的思路是 : 对指数进行折半 , 比如在我们数学考试过程中 , 要我算 2^12 , 我会转化为
2^6 x 2^6 来算 ,因为 2^12 我一时间 算不出来 ,但 2^6 我会 。同理 , 代码的思想也是这样的 , 要求 x^n , 就先求 x ^(n/2) , 要求 x^(n/2) 就求 x^(n/4) , 依此类推 。(注意 n/2 向下取整), 我们看出这个也是具有递归性的 !
最后 , 对指数 n 讨论奇偶性
- 指数 n 为偶数 ,只需求指数为 n/2 时的值 ,之后对这个值进行平方操作
- 指数 n 为 奇 数 ,只需求指数为 n/2 时的值 , 该值平方后多乘一个底数x
面试官 : 请说说第二种思路
我: 好的 。
function g(x, n) {
let ans = 1;
while (n > 0) {
if (n & 1) {
ans *= x;
}
x *= x;
n >>= 1;
}
return ans;
}
面试官 : 行 , 这道题就到这里 , 我们再来看看另一道题
我 : 好 👀(内心 : 放马过来吧)
拷打中 ...