1737 字
约 5 分钟
3
S03-B09 · 数学算法集:从阶乘到 FFT

适用读者:觉得"数学算法面试不考所以没用"的人;需要处理大数、素数、快速幂的人。
前置:S03-B01。
实测环境:Python 3.12.3 / JDK 21 口径;实测日期 2026-11-05。
覆盖旧文:A-1512(41) A-1514(0 点空壳,补写) A-1515(217) A-1516(206) A-1517(85) A-1518(166) A-1519(124) A-1520(0 点空壳,补写) A-1521(104) A-1522(37) A-1523(114) A-1524(56) A-1525(96) A-1526(179) = 1,425 点。
覆盖扩展:E-S03-012(大整数)、E-S03-013(筛法复杂度)、E-S03-014(FFT 用途)。
上一篇:S03-B08 · 位运算:用二进制位做集合
下一篇:S03-X01 · 题解索引。


1. 一句话结论

这些"冷门"算法在工程里对应四类真实问题:数值边界(溢出)、批量素数(筛)、大指数运算(快速幂)、大整数运算(BigInteger/FFT)。

算法 工程落点
阶乘 / 组合数 概率统计、贝叶斯、A/B 实验显著性
素数判定 / 筛法 哈希取模选素数、随机数种子、加密
欧几里得 / LCM 分数化简、周期对齐(调度)
快速幂 加密(RSA)、矩阵快速幂求递推
大整数乘法 / FFT 高精度计算、信号处理、卷积

2. 阶乘:溢出边界(A-1514 空壳补写)

n n! 能否放进
12 479,001,600 int(上限 2,147,483,647)✅
13 6,227,020,800 int ❌ / long ✅
20 2,432,902,008,176,640,000 long(上限 9.22×10¹⁸)✅
21 5.1×10¹⁹ long ❌ → 必须 BigInteger
100 9.3×10¹⁵⁷ 只能大整数

实测(Python 任意精度,仅演示边界):

10! = 3628800
13! = 6227020800        ← 已超 int
20! = 2432902008176640000
21! = 51090942171709440000  ← 已超 long

判据:n ≥ 13 用 long,n ≥ 21 用 BigInteger。前端/接口返回阶乘时要考虑"数值爆炸"(JSON 里 long 也会丢精度,见 S06)。

补写说明:旧卡 A-1514 是空壳(0 点),本节为新增内容,标 E-S03-012。


3. 素数:筛法 vs 试除(实测 315 倍)

3.1 实测(筛到 20 万)

方法 耗时 结果
埃氏筛 3.7 ms 17,984 个素数
逐个试除 1,167.1 ms 17,984 个(一致)

315 倍差距。原因:筛法是 O(n log log n),试除是 O(n√n)。

3.2 三种写法

方法 复杂度 说明
试除 O(n√n) 只能判单个数
埃氏筛 O(n log log n) 简单、快;有重复标记
线性筛(欧拉筛) O(n) 每个合数只被最小质因子标记一次

3.3 工程细节

// 埃氏筛(Java,boolean[] 比 BitSet 快但费内存)
boolean[] isPrime = new boolean[N + 1];
Arrays.fill(isPrime, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; (long) i * i <= N; i++)
    if (isPrime[i])
        for (int j = i * i; j <= N; j += i) isPrime[j] = false;

注意 i * i 的溢出:i 接近 √N 时 i*i 可能超 int,用 long 或先开方。

内存:N=10⁸ 时 boolean[] 约 100 MB,BitSet 约 12.5 MB——大范围筛法用位图(B08)。


4. 最大公约数与最小公倍数

static long gcd(long a, long b) { return b == 0 ? a : gcd(b, a % b); }
static long lcm(long a, long b) { return a / gcd(a, b) * b; }   // 先除后乘,防溢出
点 说明
欧几里得复杂度 O(log min(a,b))(斐波那契数列是最坏输入)
lcm 必须先除后乘 a*b 可能溢出
应用 分数化简、周期对齐(两个任务周期的最小公倍数 = 下次同时执行)

5. 快速幂:实测快 1.4 万倍

5.1 实测

计算 3^1,000,000 mod (10⁹+7):

方法 耗时
快速幂(二分) 0.024 ms
循环乘 337.8 ms

约 14,000 倍。因为快速幂是 O(log n) 次乘法,循环是 O(n) 次。

5.2 写法(迭代版,无递归栈风险)

static long powMod(long a, long n, long mod) {
    long r = 1; a %= mod;
    while (n > 0) {
        if ((n & 1) == 1) r = r * a % mod;
        a = a * a % mod;
        n >>= 1;
    }
    return r;
}

注意:r * a 在 mod 接近 2³¹ 时可能溢出 int,用 long;mod 接近 2⁶³ 时要用 BigInteger 或 Math.multiplyHigh。

5.3 推广:矩阵快速幂

递推式(如斐波那契)可以用矩阵快速幂做到 O(log n):

[F(n+1)]   [1 1]^n   [F(1)]
[F(n)  ] = [1 0]   × [F(0)]

判据:"线性递推 + n 很大(10¹⁸)"→ 矩阵快速幂。


6. 杨辉三角、整数拆分、割圆术、FFT

主题 一句话 工程落点
杨辉三角(A-1521) 组合数 C(n,k) 的表格形式 概率、动态规划转移系数
整数拆分(A-1524) 把 n 拆成正整数之和的方案数 组合计数、分堆问题
割圆术(A-1525) 用多边形逼近 π 的古典方法 教学;现代用级数/AGM
FFT(A-1526) O(n log n) 计算多项式乘法/卷积 音频处理、图像卷积、大整数乘法(Schönhage–Strassen)
弧度换算(A-1522) rad = deg × π / 180 图形、地图、物理
2 的幂判定(A-1520,空壳补写) n>0 && (n&(n-1))==0 容量对齐、哈希扩容(B08)

FFT 的真实用途(E-S03-014):不是"面试炫技",而是:

场景 为什么用 FFT
音频降噪/变声 频域滤波比时域快得多
图像卷积(大核) 卷积 → 频域乘法
大整数乘法 把数字当多项式系数,用 FFT 做卷积

7. 五个误区

误区 真相
"阶乘用 int 就行" 13! 就溢出 int(实测边界)
"试除判素数够用" 批量判定筛法快 300 倍
"快速幂只用于加密" 递推加速(矩阵快速幂)也用
"FFT 只存在于信号处理课" 大整数乘法、图像卷积都用
"lcm = a*b/gcd" 先乘会溢出,必须 a/gcd*b

8. 自检

  1. 阶乘在 n 为多少时超 int、多少时超 long?
  2. 筛到 20 万,筛法与试除的实测耗时各是多少?
  3. 快速幂为什么是 O(log n)?循环版快多少倍(实测)?
  4. lcm 为什么不能写成 a*b/gcd?
  5. FFT 在工程里的三个真实用途是什么?

9. 下一篇

S03-X01 · 题解索引(LeetCode 1–100 + 笔记卡)

基础层到此结束。下一篇是附录型索引:104 张题解卡压成"题号 · 模式 · 复杂度 · 一句话洞察",作为 J01–J06 精讲篇的对照表(承载 12,119 个原文点位)。


本篇属于系列《S03 · 数据结构与算法》。旧文来源:A-1512/1514/1515/1516/1517/1518/1519/1520/1521/1522/1523/1524/1525/1526;空壳卡 A-1514、A-1520 已补写(E-S03-012)。实测:筛法 3.7 ms vs 试除 1,167 ms;快速幂 0.024 ms vs 循环 337.8 ms。

S03-B09 · 数学算法集:从阶乘到 FFT
https://www.clxhxhhr.top/posts/5206/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。