如果你刷过算法题,第一次接触“快速幂”这个词,多半是在一道看起来简单、实则折磨人的题目里:给定整数 a、b、m,求 a^b mod m,其中 b 可能大到 10^18。我第一次遇到这种题时,第一反应是老老实实写个 for 循环,结果一提交,屏幕上赫然两个红色大字:TLE。也是从那次开始,我才认真研究了快速幂算法,也理解了为什么它能让指数运算从“跑不完”变成“眨个眼就算完”。这篇文章就把我从头到尾的理解、代码、踩坑和调试经验整理出来,希望能帮你少走弯路。
1. 从一次TLE说起:朴素幂运算的性能瓶颈
1.1 那道让我超时的题目
那年我还在为竞赛刷题,碰到的题大概是这样的:输入 a、b、m,输出 (a^b) % m,范围给得挺大方,a、m 在 int 范围内,b 最大能到 10^18。看到这个数据范围,我当时没太当回事,心想不就是循环 b 次乘 a 再取模嘛。于是写了下面这段标准“萌新代码”:
cpp复制long long naivePow(long long a, long long b, long long mod) {
long long res = 1;
for (long long i = 0; i < b; i++) {
res = res * a % mod;
}
return res;
}
逻辑没问题,乘一次取一次模,结果不会溢出,答案也正确。但问题就出在那个 for (long long i = 0; i < b; i++) 上。b 是 10^18,循环次数就是 10^18,哪怕一台机器每秒能执行 10^9 次循环,也要跑 10^9 秒,换算过来是三十多年。评测机当然不会给你三十多年,一般一秒钟等不到就 TLE 了。
从那以后我明白了一件事:在算法题里,“正确”只是及格线,“能通过数据范围”才是真正的目标。如果你只是做一次普通计算,b 是几百几千,朴素循环完全没问题;但一旦 b 到了 10^9、10^18,就必须换思路。
1.2 为什么循环次数会决定生死
很多人把 TLE 简单归结为“数据太大”,其实更准确地说,是算法的时间复杂度撑不住。朴素幂运算要做 b 次乘法取模,时间复杂度是 O(b)。当 b 是线性级别增长时,运行时间也随之线性增长,这在 b 是 10^18 的规模下完全不可接受。
我后来习惯用一个类比来理解这件事:朴素循环就像你要从 1 数到 10^18,一次只能加 1;而快速幂算法更像你手里有一个计算器,可以直接按几次平方和乘法把结果凑出来。前者是“付出和规模成正比”,后者是“付出只和规模的二进制位数成正比”。
用数据说话:10^18 大约等于 2^60,而快速幂的循环次数大约就是 60 次左右。从 10^18 次降到 60 次,这不是快了几倍,是快了几十亿倍。这种量级的优化,才是竞赛题和很多真实工程场景真正依赖的东西。
1.3 哪些场景会被指数卡住
不要觉得 10^18 的指数只存在于竞赛题里。我做项目、看开源代码时,发现很多地方其实都需要高效的大指数模幂运算:
- RSA 解密:核心公式是 m = c^d mod n,这里 d 是一个几百位的数,如果按朴素循环来算,宇宙毁灭都算不完。
- 概率和组合计数:比如求某事件发生的概率,经常要算 2^n mod p,n 是总人数或总元素个数。
- 斐波那契数列第 n 项:n 取 10^18 时,可以用矩阵快速幂把 O(n) 降到 O(log n),这也是快速幂的一个经典扩展。
- 费马小定理求逆元:要求 a^(p-2) mod p,指数是有 10^9 级别的 p,同样需要快速幂。
这些场景有个共同点:指数不是“几十”而是“巨量”。在它们面前,朴素循环不是慢,是根本跑不完。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 二进制视角:快速幂为什么能降到 O(log n)
2.1 一个手算例子:7 的 10 次方
先别急着看代码,我觉得快速幂最妙的地方在于它的数学视角。你回忆一下小学学的幂运算法则,合并同底数幂是“底数不变,指数相加”。所以 a^(x+y) = a^x * a^y。这个性质看起来平平无奇,但拿它来拆指数,就是快速幂的核心。
举个例子,计算 7^10。10 的二进制是 1010,也就是 10 = 8 + 2。所以:
7^10 = 7^(8+2) = 7^8 * 7^2
如果我手算,会先算:
- 7^2 = 49
- 7^4 = 49^2 = 2401
- 7^8 = 2401^2 = 5764801
然后 7^10 = 7^8 * 7^2 = 5764801 * 49 = 282475249。
注意到没有?我并没有按顺序乘 10 次,而是通过“反复平方”得到了 7^2、7^4、7^8,再挑出二进制中为 1 的位乘起来。整个手算只做了 3 次平方和 1 次最终乘法,比起连续乘 10 次已经少了不少,而且指数越大,这个优势越明显。
2.2 把指数拆成二进制是数学依据
任何一个正整数 b,都可以写成二进制的形式:
b = Σ (b_i * 2^i),其中 b_i 是 0 或 1
那么:
a^b = a^(Σ b_i * 2^i) = Π (a^(2^i)),其中只乘那些 b_i = 1 的项
而 a^(2^i) 这个序列有非常优美的递推关系:
a^(2^(i+1))
