)
第一章 · 总览与决策流程第二单元暴力枚举——让每一种可能都接受检查同学们欢迎回到算法王国上一单元我们绘制了算法王国的藏宝地图学会了拿到一道题时先分析题目要求、数据规模和问题特征。今天我们要认识算法王国里一支非常重要的探险队——暴力枚举队这支探险队有一个特别鲜明的特点不怕麻烦不怕重复检查只要可能的情况不多就把它们有条理地检查一遍你可能会想“这也算算法吗这不就是一个一个地试吗”没错但千万不要小看它。暴力枚举是许多复杂算法的起点。学会枚举我们不仅能解决一大批基础问题还能为以后学习 DFS、剪枝、动态规划等算法打下重要基础。今天我们将跟随小探险家进入魔法宝藏城完成以下任务理解什么是暴力枚举以及什么时候适合使用它。学会设计枚举范围避免漏掉答案。掌握单层循环、双层循环和多层循环枚举。通过典型例题学习完整的解题过程。练习枚举数字、数字对、三位数和组合方案。学会分析时间复杂度判断程序会不会太慢。完成一组自测题检查自己是否真正掌握了枚举。准备好了吗出发一、第一站什么是暴力枚举1. 国王的寻宝任务魔法国王有 5 个宝箱编号分别是1、2、3、4、5其中只有一个宝箱藏着传说中的魔法钥匙。国王告诉你钥匙一定在这 5 个宝箱中的某一个。每次只能打开一个宝箱。打开后就能知道里面有没有钥匙。请问怎样才能确保找到钥匙最简单、最可靠的办法就是打开 1 号宝箱。如果没有就打开 2 号宝箱。如果没有就打开 3 号宝箱。继续检查直到找到钥匙。因为钥匙一定在这 5 个宝箱中所以只要把所有宝箱检查完就一定不会漏掉答案。这就是暴力枚举的核心思想。2. 用一句话理解暴力枚举暴力枚举就是按照一定的规则把所有可能的候选情况逐一检查找出符合条件的答案。这里有三个重要词语所有可能 不能随意漏掉一种情况。逐一检查 每次检查一种候选方案。符合条件 不符合要求的方案要排除。注意暴力枚举并不是毫无头绪地乱试。优秀的枚举程序一定有明确的枚举范围、清楚的枚举顺序以及准确的判断条件。3. 暴力枚举的三件套第一件枚举范围我们要检查哪些候选答案例如从 1 到 100 的整数。第二件逐一尝试使用循环把每种候选情况依次拿出来。例如使用for循环检查每一个整数。第三件判断条件每次检查候选答案是否符合题目要求。例如如果一个数能被 3 整除就把它记录下来。我们可以把整个过程记成确定范围 → 逐个尝试 → 检查条件 → 记录答案。以后看到一道题如果你发现候选情况不多而且每种情况都容易检查就可以先问问自己“我能不能直接把所有可能都枚举一遍”这往往是非常好的解题起点。二、第二站最简单的枚举——检查每一个数字典型例题 1寻找魔法数字题目故事魔法学院的老师想找出 1 到 20 之间所有能被 3 整除的数字。请你编写程序输出这些数字。第一步分析题目我们要做什么候选数字1 到 20。判断条件能否被 3 整除。输出要求把所有符合条件的数字输出。怎样判断一个整数 i 能否被 3 整除使用取模运算符%i % 3 0这里的%表示取余数。例如6 % 3 0所以 6 能被 3 整除。7 % 3 1所以 7 不能被 3 整除。12 % 3 0所以 12 能被 3 整除。第二步设计算法我们不需要一个一个手动判断只需要让计算机替我们检查。第三步编写 C 程序#include iostream using namespace std; int main() { for (int i 1; i 20; i) { if (i % 3 0) { cout i ; } } cout endl; return 0; }运行结果3 6 9 12 15 18第四步逐步模拟当前数字ii % 3是否输出11否22否30是41否52否60是………180是191否202否程序没有提前知道答案而是从 1 检查到 20。第五步本题的核心知识这道题的枚举范围只有一个数字变量因此只需要一层循环。时间复杂度为 O(n) 这里 n 20。如果把上限从 20 改为 n 循环就大约执行 n 次。小探险家记忆卡枚举对象一个整数。枚举范围1 到 20。判断条件i % 3 0。循环层数一层。时间复杂度O(n) 。三、第三站双层循环——让两位数字骑士组队一层循环可以检查一个一个的数字。可是如果题目要求检查两个数字的组合怎么办例如从一组数字中找出和为 10 的两个数找出满足条件的两个不同编号统计有多少对数字符合要求。这时我们往往需要两层循环。典型例题 2魔法数字搭档题目故事魔法学校有 4 张数字卡片1、2、3、4老师想知道从中选出两张不同的卡片它们的数字之和恰好等于 5一共有多少种选法每张卡片只能选一次。第一步先用小学生的办法思考我们把所有可能的搭档列出来1 和 2和为 3。1 和 3和为 4。1 和 4和为 5符合2 和 3和为 5符合2 和 4和为 6。3 和 4和为 7。所以一共有两种选法( 1,4 ), ( 2,3 )答案是 2。但是如果有 100 张卡片呢有 1,000 张卡片呢我们总不能手动列出所有搭档吧让计算机来枚举第二步为什么需要两层循环假设外层循环负责选择第一张卡片内层循环负责选择第二张卡片。这就是两层循环的基本结构for (int i 1; i n; i) { for (int j 1; j n; j) { // 检查 i 和 j 组成的情况 } }外层循环每走一步内层循环就完整执行一轮。不过这段代码会检查(1,2)和(2,1)也会检查(1,1)。如果题目要求选择两张不同的卡片而且不区分选择顺序就应该避免这些重复。第三步改进枚举范围我们规定1 i j n什么意思i j两张卡片不能是同一张。只考虑i j不再重复检查交换顺序后的同一对。例如检查(1,4)后就不需要再检查(4,1)。第四步完整 C 程序#include iostream using namespace std; int main() { int a[4] {1, 2, 3, 4}; int n 4; int target 5; int cnt 0; for (int i 0; i n; i) { for (int j i 1; j n; j) { if (a[i] a[j] target) { cout a[i] a[j] endl; cnt; } } } cout 总共有 cnt 种选法 endl; return 0; }运行结果1 4 2 3 总共有 2 种选法第五步看懂双层循环的执行过程外层i内层j检查符合条件的组合01、2、3(1,4)12、3(2,3)23无3无无这里的数组下标从 0 开始所以a[0]是数字 1a[1]是数字 2以此类推。注意内层循环并不是每次都执行 4 次因为我们只允许j i所以随着i增大内层循环的次数会逐渐减少。第六步复杂度分析如果每个数字都和其他数字配对双层循环通常需要 O(n^2) 级别的时间。这段优化后的代码实际检查的组合数量为例如n4 时一共检查种组合。当 n 很大时仍然属于 O(n^2) 。小探险家记忆卡一个循环通常检查一组候选对象。两个循环可以检查两组对象组成的组合。如果组合不区分顺序可以尝试使用j i 1避免重复。双层循环通常会带来平方级的时间复杂度。四、第四站枚举中的大陷阱——漏掉、重复和越界暴力枚举看起来简单但有三个特别容易踩中的陷阱。陷阱一漏掉边界如果题目要求检查 1 到 100 的整数下面两种写法有什么区别for (int i 1; i 100; i)for (int i 1; i 100; i)第一段会检查 1 到 100第二段只检查 1 到 99。如果 100 恰好是答案第二段就会漏掉它。所以枚举范围必须严格对应题目要求。陷阱二重复检查从 1、2、3、4 中选择两个数字(1, 4)和(4, 1)是不是同一种选法如果题目不区分顺序它们就是同一种选法。这时可以让j从i 1开始避免重复。但如果题目研究的是有方向的关系例如“从城市 A 到城市 B”和“从城市 B 到城市 A”两者可能并不相同。因此是否可以去重要先看题目的规则。陷阱三数组越界如果数组中有 5 个元素int a[5] {10, 20, 30, 40, 50};合法下标是0、1、2、3、4下面的写法就有问题for (int i 0; i 5; i) { cout a[i] endl; }因为i 5时程序访问了不存在的a[5]。正确写法是for (int i 0; i 5; i) { cout a[i] endl; }如果有n个元素下标通常从 0 到n - 1所以条件应写成i n记住这句口诀范围先确认边界要小心不漏也不重下标不越界。五、第五站三层循环——寻找三位数密码魔法城堡的大门上有三个数字转盘。第一个转盘表示百位第二个表示十位第三个表示个位。国王留下了一条线索密码是一个三位数三个数字互不相同而且三个数字相加等于 6。请找出所有可能的密码这次我们需要同时枚举百位、十位和个位。典型例题 3魔法城堡的三位数密码题目要求找出所有满足下列条件的三位数是三位数。百位、十位、个位上的数字互不相同。三个数字之和等于 6。第一步怎样表示一个三位数假设三位数是abc其中a是百位数字。b是十位数字。c是个位数字。那么这个三位数的数值就是100a 10b c例如数字 123由于它必须是三位数所以1 a 9而十位、个位可以是 0 到 90 a ,b 9第二步设计三层循环我们可以这样写for (int a 1; a 9; a) { for (int b 0; b 9; b) { for (int c 0; c 9; c) { // 判断三个数字是否符合要求 } } }三层循环就像三个数字转盘外层决定百位。中层决定十位。内层决定个位。每确定前两位就尝试所有可能的个位。第三步添加判断条件三个数字互不相同需要满足a ! b a ! c b ! c三个数字相加等于 6需要满足a b c 6把条件合起来就是if (a ! b a ! c b ! c a b c 6)第四步完整 C 程序#include iostream using namespace std; int main() { int cnt 0; for (int a 1; a 9; a) { for (int b 0; b 9; b) { for (int c 0; c 9; c) { if (a ! b a ! c b ! c a b c 6) { cout 100 * a 10 * b c endl; cnt; } } } } cout 密码数量 cnt endl; return 0; }第五步程序为什么一定不会漏掉答案因为百位从 1 到 9每一种都检查。十位从 0 到 9每一种都检查。个位从 0 到 9每一种都检查。对每个三位数都判断了题目要求的两个条件。所以所有符合条件的三位数都会被找到。这正是枚举算法最重要的正确性思路只要候选范围覆盖全部可能并且判断条件准确就能找出所有符合要求的答案。第六步怎样减少无用检查刚才的程序最多检查9 * 10 * 10 900种三位数。其实当 a b 6 时个位数字 c 已经不可能是一个非负整数并使三数之和等于 6。我们可以进一步缩小搜索范围。例如for (int a 1; a 9; a) { for (int b 0; b 9; b) { for (int c 0; c 9; c) { if (a b c 6 a ! b a ! c b ! c) { cout 100 * a 10 * b c endl; } } } }这段代码虽然仍然枚举 900 种候选但判断条件已经非常清楚。如果要进一步优化可以根据前面的数字缩小后面的枚举范围。不过初学阶段先写出正确、清晰、不漏答案的程序比一开始就追求复杂的优化更重要。第七步复杂度分析三层循环分别大约执行 n 次时总操作量约为n * n * n n^3因此三层循环通常对应 O(n^3) 的时间复杂度。本题的百位只有 9 种可能十位和个位各有 10 种所以候选数量最多只有 900 种运行起来非常轻松。但是如果三个循环的范围都扩大到 100,000就不能再轻易使用这种方法了。记住循环层数是观察复杂度的重要线索但还要结合每层循环的实际范围。六、第六站用枚举找因数——数字城堡的守门员魔法数字城堡有一个守门员。它只允许某些数字通过能够整除城堡编号的数字。例如城堡编号是 12那么1 可以整除 12。2 可以整除 12。3 可以整除 12。4 可以整除 12。6 可以整除 12。12 可以整除 12。这些数字就是 12 的正因数。典型例题 4寻找城堡编号的所有因数题目输入一个正整数 n 输出它的所有正因数并统计因数的个数。第一步分析题目我们不知道答案是什么但知道正因数一定是 1 到 n 之间的整数。因此可以让i从 1 枚举到n。每次判断n % i 0如果余数是 0说明i可以整除n它就是一个因数。第二步完整 C 程序#include iostream using namespace std; int main() { int n; cin n; int cnt 0; for (int i 1; i n; i) { if (n % i 0) { cout i ; cnt; } } cout endl; cout 因数个数 cnt endl; return 0; }输入12输出1 2 3 4 6 12 因数个数6第三步模拟检查候选数i12 % i是因数吗10是20是30是40是52否60是75否………120是第四步复杂度分析循环从 1 运行到 n 因此时间复杂度为O(n)如果 n100 只需要检查 100 个候选数。如果 n10^9就需要检查约 10 亿个候选数可能非常慢。我们可以利用一个数学性质优化它因数通常成对出现。例如12 2 * 6 3 * 4如果一个因数大于它对应的另一个因数一定小于。因此我们只需要枚举到就能找到因数对。对于本题如果只要求统计因数个数还可以把每一对因数贡献的数量累加起来。不过初学者应先掌握最直接的枚举方法再理解为什么可以优化。这也是算法学习的一条重要路线先解决问题再研究怎样让它跑得更快。七、第七站枚举所有子集——小精灵的礼物袋森林里有三件礼物魔法球 A水晶 B金苹果 C小精灵可以选择其中任意几件也可以一件都不选。一共有多少种选法我们列一列选法编号选择的礼物1什么都不选2A3B4C5A、B6A、C7B、C8A、B、C一共 8 种。为什么是 8 种呢因为每件礼物都有两种选择选它。不选它。三件礼物相互独立所以总方案数是2 * 2 * 2 2^3 8如果有 n 件礼物每件都可以选或不选那么子集总数就是2^n典型例题 5枚举三件礼物的所有选法我们可以用二进制表示每件礼物的选择状态。例如000什么都不选。001只选 A。010只选 B。111三件全部选。对于三件礼物可以枚举从 0 到 7 的所有状态。#include iostream using namespace std; int main() { char gift[3] {A, B, C}; for (int mask 0; mask 8; mask) { cout 方案 mask ; for (int i 0; i 3; i) { if (mask (1 i)) { cout gift[i] ; } } cout endl; } return 0; }运行结果中的方案会依次对应方案 0 方案 1A 方案 2B 方案 3A B 方案 4C 方案 5A C 方案 6B C 方案 7A B C这里出现了位运算mask (1 i)它用来检查第i位是否为 1。第i位为 1就表示选择了对应礼物。如果你还没有学习位运算可以先理解“每件礼物只有选或不选两种状态”以后学到二进制与状态压缩时再深入学习这种写法。复杂度分析如果有 n 件礼物一共有 2^n 个子集。若每种方案都需要检查 n 件礼物那么时间复杂度通常是O(n2^n)当 n3 时只有 8 种方案。但当 n30 时子集数量已经超过 10 亿种。所以枚举子集只适合规模较小的问题或者需要配合剪枝、动态规划等方法进一步优化的情形。这也为后续的状态压缩 DP埋下了一颗小小的种子。八、变式训练同一把枚举钥匙可以打开多少扇门同学们现在我们已经学习了单层循环枚举一个数字双层循环枚举数字对三层循环枚举三位数枚举一个整数的因数枚举所有子集。接下来我们通过更多题目练习看看能不能把这些知识真正用起来。变式题 1寻找幸运数字题目魔法村庄认为能同时被 2 和 5 整除的整数是幸运数字。输入正整数 \(n\)输出 1 到 \(n\) 中所有幸运数字并统计数量。思路分析能同时被 2 和 5 整除就等价于能被 10 整除。当然我们也可以直接判断i % 2 0 i % 5 0完整程序#include iostream using namespace std; int main() { int n; cin n; int cnt 0; for (int i 1; i n; i) { if (i % 2 0 i % 5 0) { cout i ; cnt; } } cout endl; cout cnt endl; return 0; }输入50输出10 20 30 40 50 5思考升级 为什么不用两层循环因为我们只需要判断每一个数字是否满足条件不需要把两个数字组合起来。变式题 2寻找和为 10 的数字搭档题目有一组数字1 2 3 4 5 6 7 8 9请找出所有两个不同数字的组合使它们的和等于 10。每一组只输出一次。思路分析我们要检查数字对所以使用双层循环。为了避免重复j i 1参考程序#include iostream using namespace std; int main() { int a[9] {1, 2, 3, 4, 5, 6, 7, 8, 9}; for (int i 0; i 9; i) { for (int j i 1; j 9; j) { if (a[i] a[j] 10) { cout a[i] a[j] endl; } } } return 0; }输出1 9 2 8 3 7 4 6为什么没有5 5因为题目要求两个不同数字而且我们使用的是两张不同的卡片。这里只有一张数字 5 的卡片不能重复使用。思考升级 如果允许同一个数字使用两次程序应该怎样修改这时需要重新明确题目中的“两个数”是两个数值还是两个不同位置的元素。变式题 3寻找神奇的水仙花数题目三位水仙花数是指一个三位数的各位数字的立方和等于这个数本身。例如153 1^3 5^3 3^3请找出所有三位水仙花数。思路分析三位数范围是 100 到 999一共只有 900 个候选数。所以可以直接枚举每个三位数。对于三位数i百位i / 100十位i / 10 % 10个位i % 10参考程序#include iostream using namespace std; int main() { for (int i 100; i 999; i) { int a i / 100; int b i / 10 % 10; int c i % 10; if (a * a * a b * b * b c * c * c i) { cout i endl; } } return 0; }运行结果153 370 371 407这道题很好地展示了枚举的威力我们不必事先知道水仙花数在哪里只要完整检查 100 到 999就能找到所有符合条件的数字。变式题 4寻找三数之和题目给定数组1 2 3 4 5请找出所有三个不同位置的元素使它们的和等于 9。每组只输出一次。思路分析这次需要选择三个位置第一层循环选择第一个位置。第二层循环选择第二个位置。第三层循环选择第三个位置。为了不重复、不选择同一个位置我们规定i j k参考程序#include iostream using namespace std; int main() { int a[5] {1, 2, 3, 4, 5}; for (int i 0; i 5; i) { for (int j i 1; j 5; j) { for (int k j 1; k 5; k) { if (a[i] a[j] a[k] 9) { cout a[i] a[j] a[k] endl; } } } } return 0; }输出1 3 5 2 3 4时间复杂度属于 O(n^3)。对于只有 5 个数字的题目这个方法很合适如果数组有几十万个数字就需要考虑更高效的算法。变式题 5统计能整除 24 的数字题目魔法宝箱的编号是 24。请输出 24 的所有正因数并计算因数的个数。思路分析枚举 1 到 24检查24 % i 0。参考程序#include iostream using namespace std; int main() { int n 24; int cnt 0; for (int i 1; i n; i) { if (n % i 0) { cout i ; cnt; } } cout endl; cout 个数 cnt endl; return 0; }输出1 2 3 4 6 8 12 24 个数8思考升级 如果只想判断 24 是不是完全平方数还需要把 1 到 24 的每个数都检查一遍吗不需要。我们可以进一步利用平方数的性质。枚举不只是遍历数字也要学会缩小合理的搜索范围。九、复杂度分析暴力枚举到底会有多慢我们已经写了很多枚举程序。现在要学会回答一个重要问题当数据规模变大时程序还能跑得动吗1. 常见枚举方式的复杂度枚举方式典型结构时间复杂度枚举一个数字一层循环O(n)枚举数字对两层循环O(n^2)枚举三个数字三层循环O(n^3)枚举所有子集2^n 种子集通常为 O(n2^n)若逐个检查每个子集中的元素枚举所有排列n! 种排列通常至少与 n! 级别的候选数量有关最后两行要特别注意如果只枚举所有子集编号复杂度和逐个检查每个子集不是一回事。如果只计算排列数量和真正生成、输出所有排列也不是一回事。分析复杂度时要看程序究竟做了什么。2. 为什么循环层数很重要假设每一层循环都执行 n 次一层循环大约检查 n 次O(n)两层循环大约检查 n^2 次O(n^2)三层循环如果 n 100最简单的估算就是 100^31,000,000 次组合检查。大约检查 n^3 次O(n^3)循环嵌套通常会让操作数量快速增加但也有例外。例如内层循环的范围可能随外层变量改变或者循环中途提前结束。所以循环层数是一个很好的入门线索却不是全部答案。3. 用数据规模帮助判断下面是非常粗略的参考假设机器、实现方式和时间限制都比较普通。数据规模可以优先考虑的方案n 20枚举、部分子集枚举等可能可行n 1000O(n^2) 有时可行需具体估算通常优先考虑 O(nlog n)或 O(n)通常考虑O(n),还需要认真检查输入输出和内存n 很大可能需要数学性质、对数级算法或其他专门方法这些不是硬性规定例如n1000 时某些O(n^2) 程序很轻松但同时另一些程序可能因为内部操作昂贵而很慢。同样n20 的子集枚举也要注意具体复杂度。如果每个子集还要执行大量操作就不能只看 2^20 这个数字。4. 暴力枚举的优点与局限优点思路直观容易理解适合小规模问题容易验证正确性经常可以作为复杂算法的基础版本。局限当候选方案过多时可能超时枚举多个对象时复杂度增长很快如果范围或判断条件设计错误也可能漏答案或重复统计。请记住暴力枚举不是“笨办法”而是一种有明确适用范围的算法策略。在小数据问题中它常常是最简单、最可靠的选择在大数据问题中它也经常是我们寻找优化方法的起点。十、终极挑战暴力枚举自测营同学们恭喜你们已经完成了暴力枚举的基础训练现在进入自测环节。建议先独立完成题目再查看参考答案。每道题都尽量写出自己的思考过程而不只是选择一个答案。第一组基础概念题6 道基础自测题1. 暴力枚举最核心的思想是什么A. 只尝试第一个答案B. 有条理地检查所有可能的候选情况C. 只选择看起来最好的方案2. 检查 1 到 n 的每个整数是否能被 5 整除通常需要几层循环A. 一层B. 两层C. 三层3. 枚举 n 个数字的所有数字对常见的时间复杂度是什么A. O(1)B. O(n)C. O(n²)4. 从 n 件物品中每件物品都可以选或不选一共有多少种子集A. n²B. 2ⁿC. n!5. 数组有 n 个元素合法下标是 0 到 n-1。下面哪个循环范围正确A. i 0; i n; iB. i 0; i n; iC. i 1; i n; i6. 如果一道题的数据规模很小暴力枚举一定不能使用吗A. 一定不能B. 一定是最优算法C. 不能一概而论需要看题目和复杂度第二组程序阅读题题目 7数数小精灵下面的程序会输出多少个数字#include iostream using namespace std; int main() { int cnt 0; for (int i 1; i 20; i) { if (i % 4 0) { cnt; } } cout cnt endl; return 0; }题目 8寻找没有重复的数字搭档下面程序中的循环条件为什么使用j i 1而不是j 0for (int i 0; i n; i) { for (int j i 1; j n; j) { // 检查一对数字 } }题目 9找出程序的问题下面的程序想输出数组中的全部 5 个元素。它有什么问题int a[5] {2, 4, 6, 8, 10}; for (int i 0; i 5; i) { cout a[i] ; }第三组编程挑战题下面两题作为课后练习。请先自己完成再对照参考程序。挑战题 10魔法数字收藏家题目要求输入一个正整数 n 输出 1 到 n 中所有同时满足以下条件的数字能被 3 整除不能被 5 整除。最后输出符合条件的数字个数。输入示例20输出示例3 6 9 12 18 个数5参考程序#include iostream using namespace std; int main() { int n; cin n; int cnt 0; for (int i 1; i n; i) { if (i % 3 0 i % 5 ! 0) { cout i ; cnt; } } cout endl; cout 个数 cnt endl; return 0; }解析这道题只需要枚举一个数字因此使用一层循环。判断条件有两个i % 3 0能被 3 整除。i % 5 ! 0不能被 5 整除。两个条件必须同时成立所以使用。时间复杂度为 O(n) 额外空间复杂度为 O(1) 。挑战题 11宝箱搭档大搜索题目要求有 6 个数字2 4 6 8 10 12请找出所有不同位置的数字对使它们的和等于 14。每组只输出一次并统计共有多少组。参考程序#include iostream using namespace std; int main() { int a[6] {2, 4, 6, 8, 10, 12}; int cnt 0; for (int i 0; i 6; i) { for (int j i 1; j 6; j) { if (a[i] a[j] 14) { cout a[i] a[j] endl; cnt; } } } cout 总共有 cnt 组 endl; return 0; }参考输出2 12 4 10 6 8 总共有 3 组解析因为要选择两个不同位置所以使用双层循环。j i 1保证每一组只检查一次不会把(2,12)和(12,2)当成两组。时间复杂度为 O(n^2)因为程序需要检查所有可能的数字对。十一、本单元总结你真正学会了什么今天我们从魔法宝箱出发认识了暴力枚举并把它运用到了数字筛选、数字配对、三位数密码、因数统计和子集枚举等问题中。请记住下面这张知识地图最后请把暴力枚举的核心口诀记下来范围要完整循环要清楚条件要准确边界不马虎小数据敢枚举大数据算复杂度。下一单元我们将进入算法王国的另一支探险队——贪心算法每一步都做出选择为什么有时能得到最优答案又为什么有时会掉进陷阱