LeetCode 中经典的幂运算题目——50. Pow(x, n)。这道题看似简单,只需计算 x 的 n 次幂,但隐藏着从“暴力求解”到“高效优化”的核心思路,也是面试中常考的基础算法题,适合新手入门理解“分治思想”和“迭代优化”。
先明确题目要求:实现 pow(x, n),即计算 x 的整数 n 次幂函数(n 可以是正数、负数或 0),无需考虑大数溢出问题(题目默认测试用例在有效范围内)。
一、暴力法:直观但低效的初始思路
拿到题目,最直观的想法就是“重复乘法”——如果 n 是正数,就把 x 连续乘 n 次;如果 n 是负数,就先计算 x 的 |n| 次幂,再取倒数;如果 n 是 0,直接返回 1(任何数的 0 次幂都是 1)。
对应代码就是题目中给出的 myPow_1,我们来逐行解析其逻辑:
function myPow_1(x: number, n: number): number {
// 边界处理:x为0时,0的任意正次幂都是0(0的0次幂题目默认返回1,这里已被n===0覆盖)
if (x === 0) return 0;
// 边界处理:任何数的0次幂都是1
if (n === 0) return 1;
// 处理负指数:将负指数转化为正指数,底数变为倒数
const X = n >= 0 ? x : 1 / x;
const N = n >= 0 ? n : -n;
// 初始化结果为底数(因为要乘N次,初始值为X,再乘N-1次)
let ans = X;
// 循环N-1次,累计乘法
for (let i = 1; i < N; i++) {
ans *= X;
}
return ans;
};
暴力法的优点与局限
优点:逻辑简单、代码易写,上手难度低,适合初学者理解幂运算的本质。
局限:时间复杂度太高。当 n 是一个很大的整数(比如 10^9)时,循环需要执行 10^9 - 1 次,这会导致程序超时(LeetCode 的测试用例中,n 可以达到 ±2^31 - 1,暴力法必然过不了)。
举个例子:计算 pow(2, 1000000),暴力法需要循环 999999 次,而优化后的方法只需几十次计算,效率差距天壤之别。因此,我们需要找到一种更高效的计算方式——快速幂。
二、快速幂:分治思想的高效实现
快速幂的核心思路是**“分而治之”**,将幂运算拆分成更小的子问题,通过“指数折半、底数平方”的方式,减少乘法次数。核心原理如下:
对于任意整数 n,我们可以将其拆分为:
-
若 n 是偶数:x^n = (x^2)^(n/2) —— 底数平方,指数折半,只需计算 (x²) 的 (n/2) 次幂;
-
若 n 是奇数:x^n = x * (x^2)^((n-1)/2) —— 先多乘一次当前底数,再按偶数的逻辑拆分;
-
若 n 是负数:x^n = 1 / x^(-n) —— 转化为正指数的倒数,再按上述逻辑计算。
这样一来,每次计算都能将指数规模缩小一半,时间复杂度从暴力法的 O(n) 优化到 O(log n),即使 n 是 10^9,也只需约 30 次计算,效率极大提升。
迭代版快速幂(myPow_2 解析)
题目中给出的 myPow_2 是快速幂的迭代实现(比递归实现更节省栈空间,避免递归深度过大导致栈溢出),我们逐行拆解逻辑:
function myPow_2(x: number, n: number): number {
// 边界处理:任何数的0次幂都是1
if (n === 0) return 1;
// 结果初始化:初始值为1,后续累计乘法
let res = 1;
// 取n的绝对值,统一按正指数处理,最后再处理负指数
let absN = Math.abs(n);
// 循环条件:指数折半到0时停止
while (absN > 0) {
// 若当前指数是奇数,需要多乘一次当前的底数(对应奇数拆分逻辑)
if (absN % 2 === 1) {
res *= x;
}
// 底数平方(对应指数折半后的底数更新)
x *= x;
// 指数折半(向下取整,处理奇数时(n-1)/2等价于Math.floor(n/2))
absN = Math.floor(absN / 2);
}
// 若n是负指数,返回结果的倒数;否则直接返回结果
return n > 0 ? res : 1 / res;
};
迭代快速幂的执行流程(举例理解)
以 pow(2, 5) 为例,一步步看代码如何执行:
-
初始值:res=1,absN=5,x=2;
-
第一次循环(absN=5>0):absN是奇数(5%2=1),res=12=2;x=22=4;absN=Math.floor(5/2)=2;
-
第二次循环(absN=2>0):absN是偶数,res不变(还是2);x=4*4=16;absN=Math.floor(2/2)=1;
-
第三次循环(absN=1>0):absN是奇数,res=216=32;x=1616=256;absN=Math.floor(1/2)=0;
-
循环结束,n=5>0,返回res=32(正确结果:2^5=32)。
再举一个负指数例子:pow(2, -3):
-
初始值:res=1,absN=3,x=2;
-
循环执行后,res=8(对应2^3=8);
-
n=-3<0,返回1/8=0.125(正确结果:2^-3=1/8)。
三、两种方法对比总结
| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 暴力法(myPow_1) | O(n) | O(1) | 逻辑简单、代码易写 | 效率低,n 较大时超时 |
| 快速幂(myPow_2) | O(log n) | O(1) | 效率极高,适配大指数 | 逻辑稍复杂,需理解分治思想 |
四、关键注意点与拓展
-
边界处理:n=0 时,无论 x 是什么(除了 0^0,题目默认返回 1),都返回 1;x=0 时,若 n>0 返回 0,n<0 会出现数学错误(但题目测试用例大概率不会包含这种情况)。
-
负指数处理:核心是“转化为正指数 + 取倒数”,避免直接处理负指数导致逻辑混乱。
-
指数折半的细节:Math.floor(absN / 2) 等价于整数除法(absN >> 1,位运算效率更高),可以替换优化,但可读性稍差。
-
递归版快速幂:除了迭代实现,也可以用递归实现(分治思想更直观),但递归深度会达到 O(log n),当 n 极大时可能出现栈溢出,因此迭代版更推荐在实际开发中使用。
五、总结
LeetCode 50. Pow(x, n) 的核心是“优化幂运算的效率”,从暴力法的 O(n) 到快速幂的 O(log n),本质是分治思想的应用——将大问题拆分成小问题,通过重复利用中间结果减少计算次数。
对于新手来说,建议先理解暴力法的逻辑,再思考“为什么暴力法效率低”,进而推导快速幂的优化思路,结合例子手动模拟执行流程,就能轻松掌握。这道题不仅是一道算法题,更能帮助我们理解“优化”的本质:不是推翻重来,而是找到问题的规律,用更巧妙的方式减少冗余计算。