← 目录 / 算法文档 · 模块二 基础算法思想 / 2.1 枚举法

2.1 枚举法

不知道答案是什么,但知道答案藏在一个有限的范围里——那就把这个范围里的每一个可能都试一遍。

本页目录
① 什么是枚举法

枚举法的思路非常朴素:如果不知道答案具体是什么,但知道答案一定落在某个有限的范围里,那就把这个范围里的每一个可能"挨个试一遍",每试一个就检查它是否满足题目的条件——满足就留下,不满足就跳过。试完整个范围,答案自然就出来了。

其实模块一已经在不知不觉中多次用过这个思路了:1.3 节判断质数最基础的写法,是把 2n-1 之间的每个数都拿来试除;1.4 节求最大公约数最基础的写法,是把 min(a,b)1 之间每个数都拿来试一遍能不能同时整除两个数;1.5 节求最小公倍数,是把较大数的倍数一个个列出来试。这些"基础写法"全部都是枚举法——这一节要做的,是把这个套路单独拿出来,正式地讲一遍。

② 单层枚举:鸡兔同笼

经典的"鸡兔同笼"问题:笼子里关着鸡和兔,一共数到 35 个头94 只脚,问鸡和兔各有多少只?

用代数能解,但用枚举法更简单:鸡的数量一定在 035 之间(不可能比总头数还多)。只要枚举鸡的数量 chicken,兔子的数量自然就是 35 - chicken(因为头数加起来必须是 35);再检查这种情况下脚的总数是不是正好 94——满足就是答案。

C++ · 枚举法解鸡兔同笼
1int heads = 35, legs = 94;
2for (int chicken = 0; chicken <= heads; chicken++)
3{
4 int rabbit = heads - chicken; // 头数加起来必须是35
5 if (chicken * 2 + rabbit * 4 == legs)
6 {
7 cout << "鸡:" << chicken << " 兔:" << rabbit << endl;
8 }
9}
枚举鸡的数量,挨个检查脚的总数
chickenrabbit = 35-chicken脚的总数是否等于 94?
035140不等,跳过
134138不等,跳过
… 中间还要试 21 次,都不满足 …
221396不等,跳过
231294✓ 正好相等!找到答案
枚举到 chicken=23 时,rabbit=12,脚的总数 23×2+12×4=46+48=94,正好对上——鸡 23 只,兔 12 只。整个过程只试了 24 次chicken 从 0 到 23),比凑代数方程直接好懂。
📌
枚举的关键不是"试所有变量",而是"试到刚好够用的那一个"。这道题表面上有两个未知数(鸡、兔),但只要枚举其中一个(鸡的数量),另一个(兔的数量)就被头数的限制唯一确定了,根本不需要单独再枚举一遍。能少枚举一个变量,代码就少一层循环,速度也快得多。
③ 多层枚举:嵌套循环试遍所有组合

但有些问题,几个未知数之间互相独立,没法用一个把另一个"顶"出来,这时候就需要嵌套循环——外层循环枚举第一个变量,内层循环在外层每一种取值下,再枚举第二个变量,把所有组合都试一遍。

经典例子:找出 120 之间所有的勾股数组——也就是三个数 a < b < c,满足 a² + b² = c²(直角三角形两条直角边的平方和等于斜边的平方,比如经典的 3、4、5)。

C++ · 嵌套循环找勾股数组
1int limit = 20;
2for (int a = 1; a <= limit; a++)
3{
4 for (int b = a + 1; b <= limit; b++) // b 从 a+1 开始,保证 a<b
5 {
6 int cSquare = a * a + b * b;
7 int c = (int)sqrt(cSquare);
8 if (c * c == cSquare && c <= limit)
9 {
10 cout << a << " " << b << " " << c << endl;
11 }
12 }
13}
⚠️
判断"是不是整数的平方根"要小心精度问题:第 7 行用 sqrt() 算出来的是浮点数,直接转成 int 可能因为精度误差差 1(比如该是 5 却算成 4.999999)。第 8 行用 c * c == cSquare 再做一次整数验证,就是为了避免被这种浮点误差坑——这和 1.3 节判断质数时强调的 i * i <= n 是同一个道理:能用整数运算验证的地方,尽量不要依赖浮点数。
1~20 范围内,嵌套循环试过的部分组合(a 行 × b 列)
4
5
8
10
12
15
16
20
a=3
5
a=5
13
a=6
10
a=8
17
a=9
15
a=12
20
每一个绿色格子表示一组成功的勾股数——比如 a=3,b=4 那一格写着 5,表示 3²+4²=5²,是一组合法的勾股数组。这张表只展示了命中的部分,实际代码会把 a 从 1 到 20、ba+1 到 20 的所有组合都检查一遍——一共检查了 190 对组合,最终找到 6 组勾股数:(3,4,5) (5,12,13) (6,8,10) (8,15,17) (9,12,15) (12,16,20)
④ 枚举的效率问题

枚举法最大的优点是简单可靠,缺点是——尤其是嵌套循环,每多一层循环,总的检查次数往往是相乘而不是相加。如果 ab 各自的范围是 n,双层枚举大约要检查 次;三层枚举大约要检查 次——范围一旦变大,次数会爆炸式增长。

竞赛里有一个常用的粗略估算:一台普通电脑每秒大约能执行 1 亿(10⁸)次简单运算。题目通常会限制程序运行时间在 1~2 秒以内,所以枚举的总次数最好控制在 1 亿次左右或以下,超过太多就可能超时。

双层枚举的检查次数,随范围增长得有多快
n = 100
约 1 万次
✓ 完全没问题
n = 1000
约 100 万次
✓ 完全没问题
n = 10000
约 1 亿次
⚠ 接近上限,要留意
n = 100000
约 100 亿次
✗ 大概率超时
同样是"双层枚举",n 从 1 万变成 10 万(只大了 10 倍),检查次数却从 1 亿变成了 100 亿(大了 100 倍)——这就是嵌套循环"相乘"的威力,增长速度比想象中快得多。遇到范围较大的题目,枚举法往往不够用,需要换更聪明的思路(前面模块一学的优化技巧,以及后面要学的二分、动态规划等,都是为了避开这种"硬枚举"的代价)。
💡
缩小枚举范围,往往比换算法更简单。就像鸡兔同笼那样,能用一个变量"顶替"另一个,就尽量少枚举一层;能从更紧的范围开始(比如 1.3 节"只检查到 √n"),就不要从头开始检查。很多时候,枚举法本身没有错,错的是枚举的范围或层数没有压缩到位。
⑤ 完整代码与小结
类型特点典型例子
单层枚举一个变量被另一个变量的关系唯一确定鸡兔同笼、1.3~1.5 节的基础写法
多层枚举多个变量互相独立,需要嵌套循环试遍组合找勾股数组、找满足条件的数字组合
🎯
枚举法是几乎所有算法思路的起点——遇到一道新题,先想"能不能枚举出来",往往是最快确认"这题到底在问什么"的办法,哪怕最终因为范围太大需要换更快的算法,枚举出来的暴力解法也常常能帮你先拿到部分分数,或者用来验证正式算法的结果是否正确。