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

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

相关推荐
江畔柳前堤6 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore8 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考9 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX10 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面10 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号11 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽11 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI11 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者13 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者13 小时前
【征程6】校准量化中HistogramObserver解析
算法