LeetCode 50. Pow(x, n):从 O(n) 到 O(log n) 的快速幂彻底搞懂

题目是实现一个函数 pow(x, n),计算 xnx^nxn。约束里 n 可以到 ±231−1\pm 2^{31}-1±231−1,如果用最朴素的「循环 n 次相乘」,时间复杂度是 O(∣n∣)O(|n|)O(∣n∣),在这个范围明显不可接受。leetcode

这篇就从「最直观的数学思路」开始,一步步推到快速幂,让你真的明白为什么是 O(log⁡n)O(\log n)O(logn)。


朴素做法:按定义来,O(n)

最直观的定义是:

xn=x⋅x⋅x⋯x⏟n 个x^n = \underbrace{x \cdot x \cdot x \cdots x}_{n\ \text{个}}xn=n 个x⋅x⋅x⋯x​​

实现思路:

  • 准备一个 result,从 1 开始。

  • 循环 n 次,每次乘上一个 x。

这个做法的特点:

  • 思路简单,完全符合直觉。

  • 时间复杂度是 O(∣n∣)O(|n|)O(∣n∣)。当 ∣n∣|n|∣n∣ 接近 10910^9109 时,完全跑不动。leetcode

所以我们需要换一个角度看「幂」,而不是一口一口「吃 x」。


换个视角:把 n 拆成若干个 2 的幂

关键想法不是「乘 n 个 x」,而是「用几块大积木拼出 xnx^nxn」。

积木的规格只有这些:

x1, x2, x4, x8, x16, x32, ...x^1,\ x^2,\ x^4,\ x^8,\ x^{16},\ x^{32},\ \dotsx1, x2, x4, x8, x16, x32, ...

也就是所有 x2kx^{2^k}x2k 这种形式。

数学上的事实:

任何正整数 n,都可以唯一写成若干个不同的 2 的幂之和。

例如:

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

  • 10=8+210 = 8 + 210=8+2

  • 7=4+2+17 = 4 + 2 + 17=4+2+1

于是:

  • x13=x8+4+1=x8⋅x4⋅x1x^{13} = x^{8+4+1} = x^8 \cdot x^4 \cdot x^1x13=x8+4+1=x8⋅x4⋅x1

  • x10=x8+2=x8⋅x2x^{10} = x^{8+2} = x^8 \cdot x^2x10=x8+2=x8⋅x2

  • x7=x4+2+1=x4⋅x2⋅x1x^7 = x^{4+2+1} = x^4 \cdot x^2 \cdot x^1x7=x4+2+1=x4⋅x2⋅x1

这就是快速幂的核心:只要我能高效拿到这些「规格为 2k2^k2k 的幂积木」,再选对几块乘起来,就能得到 xnx^nxn


base 和 result:谁负责什么?

快速幂里有两个核心变量:

  • base:当前这一轮代表的「幂积木」。

  • result:到目前为止已经选中的那些积木的乘积,也就是部分结果。

职责分工:

  • base 从 x1x^1x1 开始,每一轮变为自己的平方:

    • 第 1 轮:base = x1x^1x1

    • 第 2 轮:base = x2x^2x2

    • 第 3 轮:base = x4x^4x4

    • 第 4 轮:base = x8x^8x8

    • ...

    第 k 轮的 base 是 x2k−1x^{2^{k-1}}x2k−1。

  • result 一开始是 1(什么都没选)。

    在某一轮,如果当前这块积木是「需要的」,就做:

    result∗=base\text{result} \mathrel{*}= \text{base}result∗=base

否则就跳过。

可以用一句话记住这点:

base 在「生产积木」,result 在「选择积木」。

base 可以每轮平方,result 不能每轮随便平方。

只要这句不忘,就不会写出错误的实现。


怎么知道当前这块积木要不要选?

引入一个变量 e 表示「还没处理完的指数」,一开始 e = |n|。

每一轮做三件事:

  1. 看 e 是否为奇数:

    • 如果 e 是奇数,说明目前的指数里有一块「单独的 1 次幂」需要用,这一块恰好对应当前 base:

      • result *= base
  2. 把能成对的部分合并:

    • base *= base(利用 x2k=(xk)2x^{2k} = (x^k)^2x2k=(xk)2,把成对的幂打包成新的积木)
  3. 减少还没处理的指数:

    • e /= 2(向下取整,相当于把已经处理完的这一位丢掉)

循环到 e 变成 0 为止,所有需要的积木都已经选完,result 就是 x∣n∣x^{|n|}x∣n∣。


用 x13x^{13}x13 走一遍

以 n = 13 为例,13 的分解是 13=8+4+113 = 8 + 4 + 113=8+4+1,我们应该选的积木是 x1,x4,x8x^1, x^4, x^8x1,x4,x8。

初始化:

  • base = x

  • result = 1

  • e = 13

按轮数走:

轮次 e 进入时 e 奇偶 base 进入时 操作 result 出来时
1 13 奇数 x1x^1x1 result = base x1x^1x1
base = base² = x2x^2x2
e = 13 / 2 = 6
2 6 偶数 x2x^2x2 跳过 result x1x^1x1
base = x4x^4x4
e = 6 / 2 = 3
3 3 奇数 x4x^4x4 result = base x5x^5x5
base = x8x^8x8
e = 3 / 2 = 1
4 1 奇数 x8x^8x8 result = base x13x^{13}x13
base = x16x^{16}x16
e = 1 / 2 = 0

可以看到:

  • 用到的 base 是 x1,x4,x8x^1, x^4, x^8x1,x4,x8,刚好对应 13=1+4+813 = 1 + 4 + 813=1+4+8。

  • 一共 4 轮,复杂度是 O(log⁡13)O(\log 13)O(log13)。


为什么不能"每轮都乘一次 base"?

假设循环了 k 轮,base 依次是:

x1,x2,x4,x8,...,x2k−1x^1, x^2, x^4, x^8, \dots, x^{2^{k-1}}x1,x2,x4,x8,...,x2k−1

如果你每一轮都做 result *= base,那 result 会变成:

x1+2+4+⋯+2k−1=x2k−1x^{1+2+4+\dots+2^{k-1}} = x^{2^k - 1}x1+2+4+⋯+2k−1=x2k−1

问题:

  • 指数固定是 2k−12^k - 12k−1,只能算出 1, 3, 7, 15, 31...,不能覆盖一般的 n。

  • 完全没用上「n 的具体值」,只看了循环跑了几轮。

所以在快速幂的框架里:

"每轮都更新 result" 在数学上就是错的,等于把所有积木都拿了,而正确做法是像凑钱一样,只拿能刚好拼出 n 的那几张硬币。


负指数和边界情况

完整实现里还要处理两个细节。

1. 负指数

如果 n < 0,可以先计算 x−∣n∣x^{-|n|}x−∣n∣,最后取倒数:

xn=x−∣n∣=1x∣n∣x^n = x^{-|n|} = \frac{1}{x^{|n|}}xn=x−∣n∣=x∣n∣1​

实现上的常见处理:

  • 记录 isNegative = (n < 0)

  • 用 64 位整型保存 absN,避免最小 32 位整数取反溢出。

  • 循环里只处理非负指数 absN,最后如果 isNegative 为真,返回 1 / result。leetcode

2. n = 0

任何非 0 数的 0 次幂是 1,直接返回 1 即可。leetcode


算法小结

快速幂可以用一句话总结:

用 base 依次生成 x1,x2,x4,x8,...x^1, x^2, x^4, x^8, ...x1,x2,x4,x8,... 这些「2 的幂次积木」,用 result 按照指数 n 的拆分结果,挑出其中若干块相乘;每一轮都把指数缩小一半,所以整体复杂度是 O(log⁡∣n∣)O(\log |n|)O(log∣n∣)。leetcode

更程序化地描述就是:

  1. 处理 n = 0,直接返回 1。

  2. 如果 n < 0,先把 n 变为正数,记住最后要取倒数。

  3. 初始化:

    • result = 1

    • base = x

    • e = |n|

  4. 当 e > 0:

    • 如果 e 是奇数,result *= base

    • base *= base

    • e /= 2

  5. 如果原来的 n 为负,返回 1 / result,否则返回 result。leetcode

理解了「积木」和「硬币凑钱」这两个类比之后,快速幂就不再是一个需要死记硬背的模板,而是一个非常自然的数学过程。

相关推荐
吃好睡好便好7 小时前
在Matlab中绘制横直方图
开发语言·学习·算法·matlab
仰泳之鹅8 小时前
【C语言】自定义数据类型2——联合体与枚举
c语言·开发语言·算法
x_yeyue10 小时前
三角形数
笔记·算法·数论·组合数学
念何架构之路11 小时前
Go语言加密算法
数据结构·算法·哈希算法
AI科技星11 小时前
《数学公理体系·第三部·数术几何》(2026 年版)
c语言·开发语言·线性代数·算法·矩阵·量子计算·agi
失去的青春---夕阳下的奔跑11 小时前
560. 和为 K 的子数组
数据结构·算法·leetcode
黎阳之光12 小时前
黎阳之光:以视频孪生重构智慧医院信息化,打造高标项目核心竞争力
大数据·人工智能·物联网·算法·数字孪生
丷丩12 小时前
三级缓存下MVT地图瓦片服务性能优化策略
算法·缓存·性能优化·gis·geoai-up
m0_6294947312 小时前
LeetCode 热题 100-----25.回文链表
数据结构·算法·leetcode·链表
ʚ希希ɞ ྀ13 小时前
单词拆分----dp
算法