求最大公約數(GCD)與最小公倍數(LCM)是程式設計入門必修課。本文從數學關係出發,層層遞進展示三種算法實現,並探討大數場景下的擴展思路。
設兩數 m, n,最大公約數為 a,則:
m = b * a
n = c * a
→ 最小公倍數 = a * b * c = m * n / a
因此 LCM(m,n) = m * n / GCD(m,n),核心問題歸結為求 GCD。
int gcd_brute(int m, int n) {
int smaller = min(m, n);
int gcd = 1;
for (int i = 2; i <= smaller; i++)
if (m % i == 0 && n % i == 0) gcd = i;
return gcd;
}
複雜度:O(min(m,n)),大數時極慢。
核心定理:gcd(m, n) = gcd(n, m % n),反覆替換直到餘數為 0。
int gcd_euclid(int m, int n) {
while (n != 0) {
int r = m % n;
m = n;
n = r;
}
return m;
}
// 遞歸版
int gcd_recursive(int m, int n) {
return n == 0 ? m : gcd_recursive(n, m % n);
}
複雜度:O(log min(m,n)),極快,為工業標準實現。
int gcd_subtract(int m, int n) {
while (m != n) {
if (m > n) m -= n;
else n -= m;
}
return m;
}
《九章算術》記載,僅用減法,原理同輾轉相除,適合大整數模擬或無除法指令架構。
#include
#include
// 64 位支援
int64_t gcd(int64_t m, int64_t n) {
assert(m && n);
while (n) { int64_t r = m % n; m = n; n = r; }
return m;
}
int64_t lcm(int64_t m, int64_t n) {
assert(m && n);
return m / gcd(m, n) * n; // 先除後乘,防溢出
}
| 挑戰 | 解決方案 |
|---|---|
| 超出原生類型範圍 | 使用大整數庫(GMP, Boost.Multiprecision)或自實現高精度模運算 |
| 模運算性能瓶頸 | 二進制 GCD (Stein 算法):僅用位運算、減法、移位,無除法 |
| 多核並行 | 單對 GCD 難並行;批量獨立 GCD 任務可線程池並行 |
int64_t gcd_stein(int64_t u, int64_t v) {
if (u == 0) return v;
if (v == 0) return u;
int shift = __builtin_ctzll(u | v);
u >>= __builtin_ctzll(u);
do {
v >>= __builtin_ctzll(v);
if (u > v) { int64_t t = v; v = u; u = t; }
v -= u;
} while (v);
return u << shift;
}
LCM = m*n/GCD 並處理溢出(先除後乘)。m=0 或 n=0、負數處理(取絕對值)、大數擴展。GCD/LCM 看似簡單,實則蘊含豐富算法演進史:從暴力枚舉 → 輾轉相除 → 更相減損 → 二進制 GCD。工程實踐中,**迭代版歐幾里得算法配合 64 位整型與先除後乘防溢出** 已覆蓋 99% 場景。面對極大整數,引入成熟大整數庫並採用 Stein 算法是務實之選。掌握這條演進脈絡,不僅能寫出正確高效代碼,更能在面試中展現算法素養深度。