mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
1561 字
4 分钟
快速幂
2026-06-10 03:43:08

1. 问题引入#

xnx^n 最朴素的方法是把 xx 连乘 nn 次,时间复杂度为 O(n)O(n)

nn 很大时,这种线性时间复杂度的算法效率较低,容易导致超时。快速幂法可将时间复杂度降低至 O(logn)O(\log n)

我们可以从 “迭代(二进制角度)”“递归(分治法角度)” 两个角度来实现快速幂算法。


2. 方法一:迭代快速幂(二进制角度)#

2.1 算法原理#

利用整数 nn 的二进制表示,可对快速幂进行数学化解释。

对于任何十进制正整数 nn,设其二进制表示为 bmb3b2b1b_m \dots b_3 b_2 b_1(其中 bi{0,1}b_i \in \{0, 1\}i[1,m]i \in [1, m]),则有:

  • 二进制转十进制: n=1b1+2b2+4b3++2m1bmn = 1b_1 + 2b_2 + 4b_3 + \dots + 2^{m-1}b_m
  • 幂的二进制展开: xn=x1b1+2b2+4b3++2m1bm=x1b1x2b2x4b3x2m1bmx^n = x^{1b_1 + 2b_2 + 4b_3 + \dots + 2^{m-1}b_m} = x^{1b_1} x^{2b_2} x^{4b_3} \dots x^{2^{m-1}b_m}

根据以上推导,可把计算 xnx^n 转化为解决以下两个问题:

  1. 计算 x1,x2,x4,,x2m1x^1, x^2, x^4, \dots, x^{2^{m-1}} 的值: 循环赋值操作 x=x2x = x^2 即可。
  2. 获取二进制各位 b1,b2,b3,,bmb_1, b_2, b_3, \dots, b_m 的值: 循环执行以下操作:
    • n & 1(与操作): 判断 nn 二进制最右一位是否为 1。
    • n >> 1(移位操作): nn 右移一位(相当于删除最后一位)。

因此,在循环中依次处理 x20,x21,,x2m1x^{2^0}, x^{2^1}, \dots, x^{2^{m-1}},并把所有二进制位为 11 的项累计相乘即可。

快速幂二进制公式解析

下图直观地展示了迭代计算中 xx 的平方以及 nn 二进制位的变化过程:

快速幂迭代计算过程

2.2 迭代过程示例:以 3133^{13} 为例#

以上面的二进制拆分为基础,我们可以进一步观察快速幂循环中各个变量的变化过程。

对于 3133^{13}

13=1101213 = 1101_2

也就是:

13=8+4+113 = 8 + 4 + 1

所以最终应该只选择:

3134383^1 \text{、} 3^4 \text{、} 3^8

323^2 只是循环过程中产生的候选值,并不会参与最终结果相乘。

快速幂循环中,x 会不断平方,依次表示:

313234383^1 \rightarrow 3^2 \rightarrow 3^4 \rightarrow 3^8

同时,b 会不断右移,依次检查二进制的最低位是否为 1。

过程如下:

当前 b当前 xb 是否为奇数ans 变化
1331ans = 1 * 31
632ans 不变
334ans = 31 * 34
138ans = 31 * 34 * 38

因此最终:

ans=31×34×38ans = 3^1 \times 3^4 \times 3^8

也就是:

313=15943233^{13} = 1594323

从二进制角度看,13 的二进制为 110121101_2。从右往左依次检查每一位:

二进制位当前 x是否乘入 ans
131乘入
032不乘
134乘入
138乘入

所以代码中的:

if ((b & 1) == 1) {
ans *= x;
}

这一步是在判断当前二进制位是否为 1。如果是 1,就把当前的 x 乘入答案。

而:

x *= x;
b >>= 1;

分别表示:

  • x *= x:让当前幂次翻倍,例如 313234383^1 \rightarrow 3^2 \rightarrow 3^4 \rightarrow 3^8
  • b >>= 1:让指数右移一位,继续检查下一位二进制

注意: 在 Java、C++ 等语言中,32 位有符号整数 int 的范围是 [231,2311][-2^{31}, 2^{31}-1]。当 n=231n = -2^{31} 时,直接取相反数 -n 会导致溢出。解决方法是先将 nn 存入 64 位整数(例如 Java 的 long 类型)再进行计算。

2.3 代码实现#

class Solution {
public double myPow(double x, int n) {
// 0 次幂直接返回 1
if (n == 0) {
return 1.0;
}
// 0 的正整数次幂直接返回 0
if (x == 0.0) {
return 0.0;
}
long b = n;
double ans = 1.0;
// 负指数先转成倒数,再把指数变为正数
if (b < 0) {
x = 1.0 / x;
b = -b;
}
while (b > 0) {
// 当前二进制位为 1,就把当前幂次乘进答案
if ((b & 1) == 1) {
ans *= x;
}
// 当前幂次翻倍
x *= x;
// 指数右移一位,继续处理下一位
b >>= 1;
}
return ans;
}
}

2.4 复杂度分析#

  • 时间复杂度: O(logn)O(\log |n|),循环次数与 nn 的二进制位数成正比。
  • 空间复杂度: O(1)O(1),仅使用常数大小的额外空间。

3. 方法二:递归快速幂(分治法角度)#

3.1 算法原理#

快速幂实际上也是分治思想的一种典型应用。

  • n<0n < 0 时,我们可以利用倒数关系将其转换为正数次幂:xn=(1/x)nx^n = (1/x)^{-n}
  • n0n \ge 0 时,我们可以将问题划分为规模减半的子问题。设 half = dfs(x, n / 2),在 Java 中整数除法会自动向下取整,则有:

xn={(xn/2)2,n 为偶数(xn/2)2×x,n 为奇数x^n = \begin{cases} (x^{n / 2})^2, & n \text{ 为偶数} \\ (x^{n / 2})^2 \times x, & n \text{ 为奇数} \end{cases}

根据上式,我们只需要先递归地计算 xn/2x^{n / 2},然后根据 nn 的奇偶性,决定是否多乘一个 xx 即可。

3.2 递归过程示例:以 3133^{13} 为例#

上面的公式一开始可能不太直观,可以通过 3133^{13} 来理解递归快速幂的执行过程。

递归快速幂的核心是:每次先计算一半的幂次,然后根据当前指数的奇偶性决定如何合并结果

对于 3133^{13}

由于 13 是奇数,所以:

313=36×36×33^{13} = 3^6 \times 3^6 \times 3

只要先算出 363^6,就可以得到 3133^{13}

继续拆分:

36=33×333^6 = 3^3 \times 3^3

因为 6 是偶数,所以平方即可,不需要额外乘 3。

继续拆分:

33=31×31×33^3 = 3^1 \times 3^1 \times 3

因为 3 是奇数,所以平方后还需要额外乘 3。

继续拆分:

31=30×30×33^1 = 3^0 \times 3^0 \times 3

当指数为 0 时,递归到达边界,直接返回 1。

递归调用过程如下:

调用含义
dfs(3, 13)计算 313,需要先计算 36
dfs(3, 6)计算 36,需要先计算 33
dfs(3, 3)计算 33,需要先计算 31
dfs(3, 1)计算 31,需要先计算 30
dfs(3, 0)指数为 0,返回 1

返回过程如下:

返回层当前指数计算方式返回值
dfs(3, 0)0返回 11
dfs(3, 1)11 × 1 × 33
dfs(3, 3)33 × 3 × 327
dfs(3, 6)627 × 27729
dfs(3, 13)13729 × 729 × 31594323

可以看到,递归快速幂并不是从 3133^{13} 一步一步乘 13 次,而是不断把指数减半:

13631013 \rightarrow 6 \rightarrow 3 \rightarrow 1 \rightarrow 0

然后在返回时逐层合并结果。

因此,递归层数只和指数不断除以 2 的次数有关,时间复杂度为 O(logn)O(\log |n|)

  • 递归边界:n=0n = 0 时,返回 1.01.0。若 x=0.0x = 0.0n>0n > 0,直接返回 0.00.0。这里默认题目不会额外考察 00 的负指数情况。

快速幂递归分治解析

3.3 代码实现#

class Solution {
public double myPow(double x, int n) {
// 0 次幂直接返回 1
if (n == 0) {
return 1.0;
}
// 0 的正整数次幂直接返回 0
if (x == 0.0) {
return 0.0;
}
long b = n;
// 负指数先在入口处处理,dfs 只负责非负指数
if (n < 0) {
x = 1.0 / x;
b = -b;
}
return dfs(x, b);
}
private double dfs(double x, long n) {
// 非负指数的递归边界
if (n == 0) {
return 1.0;
}
double half = dfs(x, n / 2);
// 偶数次幂:直接平方
if (n % 2 == 0) {
return half * half;
}
// 奇数次幂:平方后再补一个 x
return half * half * x;
}
}

3.4 复杂度分析#

  • 时间复杂度: O(logn)O(\log |n|),每次递归使幂次减半,因此递归树的深度为对数级别。
  • 空间复杂度: O(logn)O(\log |n|),递归树的深度为 O(logn)O(\log |n|),需要占用对应大小的系统函数调用栈空间。

4. 算法对比与总结#

方法时间复杂度空间复杂度优缺点
方法一:迭代快速幂O(logn)O(\log \vert n \vert)O(1)O(1)推荐。避免了递归带来的系统栈开销,效率更高,更适用于工程实现。
方法二:递归快速幂O(logn)O(\log \vert n \vert)O(logn)O(\log \vert n \vert)代码结构更贴近数学公式的直观表达,逻辑简单清晰,但存在栈溢出的隐患。
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

快速幂
https://leetcode.cn/problems/powx-n/
作者
黎明
发布于
2026-06-10 03:43:08
许可协议
MIT

部分信息可能已经过时

目录