共计 1338 个字符,预计需要花费 4 分钟才能阅读完成。
最近在刷编程竞赛题时遇到了一个经典问题(题目编号 1036),要求同时计算两个数的最大公约数 (GCD) 和最小公倍数(LCM)。这类问题看似简单,但想要写出高效且健壮的代码,还是有不少学问的。今天就来分享一下我的解题思路和优化经验。

问题背景与暴力解法
题目给出两个整数(比如 6 和 15),要求输出它们的最大公约数和最小公倍数。最直观的暴力解法是:
- 最大公约数:从较小数开始向下枚举,找到第一个能同时整除两数的值
- 最小公倍数:从较大数开始向上枚举,找到第一个能被两数同时整除的值
这种方法在小数字时可行,但当遇到大数(比如 1e9 级别)时,效率就会变得极低。时间复杂度是 O(min(a,b)),显然不适合编程竞赛的场景。
欧几里得算法原理
更高效的解法是使用欧几里得算法,基于一个关键数学原理:
gcd(a, b) = gcd(b, a \mod b)
这个递推关系会不断缩小问题规模,直到 b = 0 时,a 就是最终结果。其时间复杂度是 O(log min(a,b)),效率提升非常明显。
数学简图说明:
假设 a =24, b=18
1. 24 ÷ 18 = 1 余 6 → gcd(18,6)
2. 18 ÷ 6 = 3 余 0 → gcd(6,0)
3. 返回 6
代码实现
Python 版本
递归实现(简洁但需要注意栈深度):
def gcd_recursive(a, b):
"""递归实现 GCD"""
return a if b == 0 else gcd_recursive(b, a % b)
迭代实现(推荐):
def gcd_iterative(a, b):
"""迭代实现 GCD,避免递归栈溢出"""
while b:
a, b = b, a % b
return a
计算 LCM 时需要注意:
def lcm(a, b):
"""计算最小公倍数"""
return a * b // gcd_iterative(a, b) # 先除后乘避免溢出
C++ 优化版
使用位运算和迭代进一步优化:
int gcd(int a, int b) {if (a == 0) return b;
if (b == 0) return a;
// 移除公因子 2
int shift = __builtin_ctz(a | b);
a >>= __builtin_ctz(a);
do {b >>= __builtin_ctz(b);
if (a > b) std::swap(a, b);
b -= a;
} while (b);
return a << shift;
}
边界条件处理
实际编码时需要特别注意:
- 处理 0 的情况:gcd(a,0)=a
- 处理负数:可以先取绝对值
- 大数乘法溢出:计算 LCM 时应该先除后乘
性能分析
欧几里得算法的时间复杂度是 O(log min(a,b)),这意味着:
- 对于 32 位整数,最坏情况下也只需约 32 次迭代
- 在 128MB 内存限制下(约 3 千万 int 空间),完全不用担心空间问题
- 1 秒时间限制内可以处理 1e18 量级的数字
常见坑点
- 递归深度:Python 默认递归深度约 1000 层,大数时可能栈溢出
- 输入验证:题目虽保证输入合法,但实际应用要检查非负整数
- LCM 溢出:a* b 可能溢出,必须先进行除法运算
进阶思考
- Stein 算法(二进制 GCD)如何进一步优化大数运算?
- 如果输入扩展为 n 个数(n≥3),该如何高效计算 GCD 和 LCM?
通过这道题目,我深刻体会到:即使是基础算法,想要写出工业级强度的代码,也需要考虑很多边界条件和优化细节。希望这篇笔记对正在准备竞赛或面试的你有所帮助!
正文完
发表至: 未分类
近三天内
