在刷 LeetCode 的过程中,我们经常会遇到各种数学题,这类题目往往无法仅靠暴力法取胜,背后的数学原理才是通关关键。本文将详细整理 LeetCode 常见数学技巧,包括理论 + 高质量 C++ 实现。
1. 乘法逆元(Modular Multiplicative Inverse)
应用场景
当你需要在模意义下“除以一个数”时,就要用它的 乘法逆元。
通常用于:
-
组合数取模
-
模线性方程求解
C++ 实现(基于费马小定理,模数必须是质数)
int modInverse(int a, int mod) {
// 快速幂实现 a^(mod-2) % mod
int res = 1;
a %= mod;
int b = mod - 2;
while (b) {
if (b & 1) res = 1LL * res * a % mod;
a = 1LL * a * a % mod;
b >>= 1;
}
return res;
}
2. 二分快速幂(Binary Exponentiation)
应用场景
当你需要计算 a^b % mod
,但 b
非常大时,用此方法可在 O(log b)
时间内完成。
int fastPow(int a, int b, int mod) {
int res = 1;
a %= mod;
while (b) {
if (b & 1) res = 1LL * res * a % mod;
a = 1LL * a * a % mod;
b >>= 1;
}
return res;
}
3. 高斯鞋带定理(Shoelace Formula)
应用场景
用于计算多边形面积,常见于几何类题目。
double polygonArea(const vector<pair<int, int>>& points) {
int n = points.size();
long long area = 0;
for (int i = 0; i < n; i++) {
int x1 = points[i].first, y1 = points[i].second;
int x2 = points[(i + 1) % n].first, y2 = points[(i + 1) % n].second;
area += 1LL * (x1 * y2 - x2 * y1);
}
return abs(area) / 2.0;
}
4. 欧拉函数(Euler's Totient Function)
应用场景
用于判断一个数 n 小于它且与它互素的整数数量。
int eulerTotient(int n) {
int res = n;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
while (n % i == 0) n /= i;
res -= res / i;
}
}
if (n > 1) res -= res / n;
return res;
}
5. 前缀和(Prefix Sum)
应用场景
用于快速查询子数组和、区间统计等问题。
vector<int> prefixSum(const vector<int>& nums) {
int n = nums.size();
vector<int> prefix(n + 1);
for (int i = 0; i < n; ++i) {
prefix[i + 1] = prefix[i] + nums[i];
}
return prefix;
}
6. 组合数(Combination)+ 预处理
应用场景
在模意义下计算组合数 C(n,k)C(n, k)C(n,k),多个查询时建议预处理。
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 10;
int fact[MAXN], invFact[MAXN];
void initComb() {
fact[0] = invFact[0] = 1;
for (int i = 1; i < MAXN; ++i) {
fact[i] = 1LL * fact[i - 1] * i % MOD;
}
invFact[MAXN - 1] = modInverse(fact[MAXN - 1], MOD);
for (int i = MAXN - 2; i >= 1; --i) {
invFact[i] = 1LL * invFact[i + 1] * (i + 1) % MOD;
}
}
int comb(int n, int k) {
if (k < 0 || k > n) return 0;
return 1LL * fact[n] * invFact[k] % MOD * invFact[n - k] % MOD;
}
7. 同余模定理(Modular Congruence)
应用场景
用于简化模等式,快速判断两个数模相等。
bool isCongruent(int a, int b, int mod) {
return (a - b) % mod == 0;
}
8. 约数个数定理(Divisor Count)
应用场景
用于找出一个数的所有正整数因子的数量。
int countDivisors(int n) {
int count = 0;
for (int i = 1; i * i <= n; ++i) {
if (n % i == 0) {
count += (i == n / i) ? 1 : 2;
}
}
return count;
}
9. 最大公约数 & 扩展欧几里得(Extended GCD)
应用场景
解决 ax + by = gcd(a, b)
的整数解,用于求解线性同余方程。
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
// 扩展欧几里得
int extendedGCD(int a, int b, int& x, int& y) {
if (b == 0) {
x = 1; y = 0;
return a;
}
int d = extendedGCD(b, a % b, y, x);
y -= a / b * x;
return d;
}
10. 埃拉托色尼筛法(Sieve of Eratosthenes)
应用场景
快速生成质数列表,适用于质数计数、质因数分解。
vector<bool> sieve(int n) {
vector<bool> isPrime(n + 1, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; i * i <= n; ++i) {
if (isPrime[i]) {
for (int j = i * i; j <= n; j += i)
isPrime[j] = false;
}
}
return isPrime;
}
11. 康托展开(Cantor Expansion)
应用场景
将一个排列映射为整数(排名),常用于状态压缩、唯一标识。
int cantor(const vector<int>& perm) {
int n = perm.size();
int res = 0;
vector<int> used(n, 0);
vector<int> fact(n, 1);
for (int i = 1; i < n; ++i) fact[i] = fact[i - 1] * i;
for (int i = 0; i < n; ++i) {
int cnt = 0;
for (int j = 0; j < perm[i]; ++j)
if (!used[j]) ++cnt;
res += cnt * fact[n - i - 1];
used[perm[i]] = 1;
}
return res;
}
12. 线性筛法(Linear Sieve)
应用场景
与埃氏筛不同,线性筛可以在 O(n) 时间内获取质数和最小质因子,适合用于分解质因数等扩展操作。
const int MAXN = 1e6;
int minPrime[MAXN]; // 记录每个数的最小质因数
vector<int> primes;
void linearSieve(int n) {
for (int i = 2; i <= n; ++i) {
if (minPrime[i] == 0) {
minPrime[i] = i;
primes.push_back(i);
}
for (int p : primes) {
if (p > minPrime[i] || i * p > n) break;
minPrime[i * p] = p;
}
}
}
13. 乘法原理(Multiplication Principle)
应用场景
用于计数问题,如排列组合构造、密码锁、数字组合问题等。
例如:长度为 n
的数字序列,每位可以从 0~9
中任选,求方案总数。
int countSequences(int n) {
return pow(10, n); // 10^n 个组合
}
14. 排列组合递推公式
应用场景
不使用阶乘时快速求组合数(可避免溢出),适合 n 较大时。
int comb(int n, int k) {
long long res = 1;
for (int i = 1; i <= k; ++i) {
res = res * (n - i + 1) / i;
}
return (int)res;
}
15. 数位 DP(Digit Dynamic Programming)
应用场景
用于计数满足特定性质的整数,例如:统计不包含某些数字的数字数量、0-9 出现频率。
int dfs(int pos, int lead, int tight, vector<int>& digits) {
// 省略具体实现,仅展示用法
// 递归考虑每一位的取值可能
return 0;
}
int countValid(int n) {
vector<int> digits;
while (n) {
digits.push_back(n % 10);
n /= 10;
}
reverse(digits.begin(), digits.end());
return dfs(0, 1, 1, digits);
}
16. 差分数组(Difference Array)
应用场景
适用于频繁修改某一段区间的值,如:区间 +1 操作、区间覆盖等。
vector<int> getModifiedArray(int length, vector<vector<int>>& updates) {
vector<int> diff(length + 1, 0);
for (auto& u : updates) {
int l = u[0], r = u[1], val = u[2];
diff[l] += val;
diff[r + 1] -= val;
}
for (int i = 1; i < length; ++i)
diff[i] += diff[i - 1];
diff.pop_back(); // 去掉多余项
return diff;
}
17. 矩阵快速幂(Matrix Fast Power)
应用场景
常用于求斐波那契数列的第 n 项、线性递推式等问题,时间复杂度 O(logN)。
struct Matrix {
long long mat[2][2];
Matrix operator*(const Matrix& other) const {
Matrix res{};
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j)
for (int k = 0; k < 2; ++k)
res.mat[i][j] += mat[i][k] * other.mat[k][j];
return res;
}
};
Matrix matrixPow(Matrix a, int n) {
Matrix res = {{{1, 0}, {0, 1}}}; // 单位矩阵
while (n) {
if (n & 1) res = res * a;
a = a * a;
n >>= 1;
}
return res;
}
18. 状态压缩(Bitmask Optimization)
应用场景
适用于 n 较小(一般 n <= 20)的组合枚举问题,如旅行商问题、集合划分、状态标记
// 统计所有子集的和
int subsetSum(vector<int>& nums) {
int n = nums.size(), total = 0;
for (int mask = 0; mask < (1 << n); ++mask) {
int sum = 0;
for (int i = 0; i < n; ++i)
if (mask >> i & 1)
sum += nums[i];
total += sum;
}
return total;
}
19. 中国剩余定理(Chinese Remainder Theorem, CRT)
应用场景
求一组同余方程的最小正整数解:
// 求解 x ≡ a1 (mod m1), x ≡ a2 (mod m2)
int CRT(vector<int>& a, vector<int>& m) {
int n = a.size();
long long M = 1, res = 0;
for (int mi : m) M *= mi;
for (int i = 0; i < n; ++i) {
long long Mi = M / m[i];
long long ti = modInverse(Mi % m[i], m[i]);
res = (res + a[i] * Mi % M * ti % M) % M;
}
return (int)(res % M);
}
20. 最小公倍数(LCM)
应用场景
经常用于模拟轮转、最短时间循环、周期重合类题目。
int lcm(int a, int b) {
return a / gcd(a, b) * b;
}