墓地里散落着许多宝石,你必须找齐四对价值相同的宝石才能逃离;月亮神殿与太阳神殿前,你要找出四对双胞胎圣骑士让他们成对祈祷。这两个任务有一个共同的核心:不再是"从数组里挑出符合条件的单个元素",而是"从数组里找出两个互相匹配的元素"。要判断"两个元素是否配对",就得把它们两两放在一起比较——这需要一种全新的循环结构:嵌套循环(nested loops)。本单元就来学习用嵌套循环做配对查找。
for i 套内层 for j 实现;用 if i == j: continue 跳过元素和自己比较;再判断这一对是否匹配——宝石看 value 是否相等,双胞胎看名字除最后一位外是否逐位相同;找到就返回这一对或说出它们的名字。
之前的过滤只需一个循环,因为"要不要这个元素"只看它自己。但配对不同——判断元素 A 要不要,得看它能不能和 B、C、D……中的某一个配上。也就是说,对每一个元素,都要把其余元素再看一遍。关卡概览说得很到位:"每次迭代所有元素时,都需要对每个元素进行迭代!"这句话正是嵌套循环的精髓。
如果数组有 n 个元素,用双重循环全部两两比较,会检查 n × n 次;去掉"自己和自己"这 n 次,剩 n × (n − 1) 次有序对;如果不区分"A 配 B"和"B 配 A",真正不同的组合只有 n × (n − 1) / 2 对。理解这个数量,能帮你明白为什么后面要用 continue 和 i + 1 来省去重复。
嵌套循环就是"循环里面再放一个循环"。外层 for i 每固定一个元素 items[i],内层 for j 就把整个数组从头到尾走一遍,让 items[i] 依次和每个 items[j] 相遇。这样,所有两两组合都不会被遗漏。
# 外层固定第一个,内层遍历第二个 for i in range(len(items)): itemI = items[i] for j in range(len(items)): itemJ = items[j] # 这里 itemI 和 itemJ 就是一对
执行顺序要理解透:内层循环要完整跑完一整圈,外层才前进一步。当 i=0 时,j 从头走到尾;然后 i=1,j 又从头走到尾……如同时钟的分针转一整圈,时针才动一格。下面这张矩阵展示了 4 个元素时,双重循环会踏过哪些 (i, j) 组合。
| i \ j | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| 0 | 跳过 | (0,1) | (0,2) | (0,3) |
| 1 | (1,0) | 跳过 | (1,2) | (1,3) |
| 2 | (2,0) | (2,1) | 跳过 | (2,3) |
| 3 | (3,0) | (3,1) | (3,2) | 跳过 |
i == j 的情形,必须跳过。其余格子才是有意义的配对——注意 (0,1) 和 (1,0) 其实是同一对宝石,说明还有优化空间(见 11.3.2)。
当 i == j 时,items[i] 和 items[j] 是同一个元素。它的 value 当然等于它自己,若不排除,程序会误以为"找到一对",实际却只有一颗宝石——在关卡里这会让你犯错、晕倒!解决办法是 if i == j: continue:continue 会跳过本轮内层循环的剩余代码,直接进入下一个 j,从而绕开自我比较。
for i in range(len(items)): for j in range(len(items)): if i == j: continue # 同一个元素,跳过 # 到这里 i、j 一定不同 ...
continue 是"跳过这一轮,继续下一轮";break 是"直接结束整个循环"。这里用 continue——我们只想跳过 i==j 这一种情况,其余组合还要继续检查,绝不能 break 把循环整个停掉。
关卡概览给出了一个更优雅的写法:让内层 for j in range(i + 1, len(array)) 从 i + 1 开始。这样 j 永远大于 i,天然就不会有 i == j,也不会重复检查已经比过的对(比如比了 (0,1) 就不会再比 (1,0))。两种写法都对:i==j: continue 直观易懂,i+1 起更高效。
for j in range(len(arr)): if i == j: continue ...
for j in range(i + 1, len(arr)): # j 恒大于 i,无重复 ...
跳过自比较后,就用 == 判断这两个不同元素是否匹配。宝石对看价值:if items[i].value == items[j].value 成立,就是一对同值宝石。这是最直接的"值匹配算法"。
一旦找到匹配的一对,就用 return [itemI, itemJ] 把这两个元素打包成数组返回——return 会立刻结束函数,不再检查后面的组合。如果双重循环全部跑完都没找到,说明没有配对,函数最后 return None。调用处再用 if gemPair: 判断有没有拿到结果。
# 值匹配 + 返回配对 def findValuePair(items): for i in range(len(items)): for j in range(len(items)): if i == j: continue if items[i].value == items[j].value: return [items[i], items[j]] # 找到! return None # 没有配对
[itemI, itemJ] 一并返回最方便。调用处 gemPair = findValuePair(gems) 拿到后,用 gemPair[0]、gemPair[1] 取出这一对,分别去移动收集。
"孪生力量"里的匹配规则更细致:双胞胎的名字长度相同、且除最后一个字母外每一位都一样(比如 "Reynaldo" 和 "Reynalda")。判断逻辑分两步:先比长度,长度不同直接不是双胞胎;再逐位比较到倒数第二位——用 for i in range(len(name1) - 1) 只遍历到 len - 1(正好跳过最后一位),任何一位不同就 return False;全都相同则 return True。这段 areTwins 函数关卡已经给好,你直接用即可。
# 判断两名字是否只差最后一位(关卡已给) def areTwins(unit1, unit2): name1 = unit1.id name2 = unit2.id if len(name1) != len(name2): return False # 长度不同,直接排除 for i in range(len(name1) - 1): # 只比到倒数第二位 if name1[i] != name2[i]: return False # 有一位不同 return True # 前面全同 → 是双胞胎
len - 1 覆盖全部字符,而 range(len - 1) 产生 0 到 len - 2——恰好不包含最后一位的下标。这正好实现"除最后一位外全部比较"的需求。这和第八单元子串匹配里"逐字符比较"的思路一脉相承。
这一节聚焦每一关的策略、思路与算法设计,只展示用于说明的关键片段,完整通关程序请你自己动手完成。
要逃离墓地,你必须收集4 对宝石,每对由两颗价值相同的宝石组成。若配错就会晕倒!好在巫师会用 "Haste"(加速)法术帮你——每收集完宝石返回起点就能获得加速。所以流程是:找出一对同值宝石 → 去捡第一颗 → 回起点加速 → 去捡第二颗 → 回起点加速。
关卡把任务拆成了两块。第一块是算法:补全 findValuePair(items) 函数,用双重循环 + 跳过自比较 + 值匹配,返回一对同值宝石。第二块是动作:在 while True 里每帧调用它,若拿到 gemPair,就按"移动到 gemA → 回起点 (40,44) → 移动到 gemB → 回起点"的顺序把这一对收入囊中,回起点是为了触发巫师的加速。
findValuePair 的算法就是 11.4 讲的模板:外层 for i、内层 for j 遍历所有组合,if i == j: continue 跳过同一颗宝石,再 if items[i].value == items[j].value 判断价值是否相等,相等就 return [items[i], items[j]],全部没找到则 return None。动作部分则用 gemPair[0]、gemPair[1] 取出两颗宝石的坐标去 moveXY,中间穿插回起点 moveXY(40, 44)。
# 关键片段:收集一对宝石的动作序列 while True: gems = hero.findItems() gemPair = findValuePair(gems) if gemPair: gemA = gemPair[0] gemB = gemPair[1] hero.moveXY(gemA.pos.x, gemA.pos.y) # 捡第一颗 hero.moveXY(40, 44) # 回起点加速 hero.moveXY(gemB.pos.x, gemB.pos.y) # 捡第二颗 hero.moveXY(40, 44) # 再回起点加速
if i == j: continue,当 i 和 j 指向同一颗宝石时,它的 value 必然等于自己,函数会误报"找到一对",可实际只有一颗。按错误结果行动就会犯错晕倒。这一句是本关的安全阀。
moveXY(40, 44) 回起点,是为了从巫师那里获得 "Haste" 加速——有了加速才能足够快地跑完全程收齐四对。这提醒我们:算法找出目标后,行动的顺序和节奏同样是通关策略的一部分。
你要引诱一只近视的巨型食人魔经过月亮神殿和太阳神殿,趁机布下陷阱。启动神殿之力的钥匙是四对双胞胎圣骑士——他们名字相同、只有最后一个字母不同。你需要找出每一对双胞胎,并把两人的名字用一个空格隔开一起说出来:hero.say(twin1.id + " " + twin2.id),唤醒日月的力量。
这一关的骨架和"无暇的宝石对"一模一样——还是双重循环遍历所有两两组合、跳过自比较。唯一的变化是"匹配条件":宝石看 value == value,双胞胎则调用关卡已给好的 areTwins(unit1, unit2)(内部做名字逐位比较)。策略就是:拿到 friends = hero.findFriends(),用嵌套循环两两配对,if f1 != f2 and areTwins(...) 成立就说出两人名字。
外层 for f1 in range(len(friends))、内层 for f2 in range(len(friends)) 遍历所有圣骑士组合;用 if f1 != f2 and areTwins(friends[f1], friends[f2]) 一次性完成"跳过自比较"和"双胞胎判断"两件事——注意这里把 f1 != f2 和 areTwins 用 and 连起来,只有既不是同一人、又确实是双胞胎,才 hero.say(friends[f1].id + " " + friends[f2].id)。呼唤完四对后,再前后移动(如 moveXY(64, 36) 与 moveXY(14, 36))来回引诱食人魔踏入陷阱。
# 关键片段:嵌套循环 + areTwins 找双胞胎 friends = hero.findFriends() for f1 in range(len(friends)): for f2 in range(len(friends)): if f1 != f2 and areTwins(friends[f1], friends[f2]): hero.say(friends[f1].id + " " + friends[f2].id)
if f1 != f2 and areTwins(...) 是一种简洁写法:f1 != f2 排除自比较,areTwins(...) 判断是不是双胞胎,二者都为真才行动。它等价于先 if f1 == f2: continue 再判断,但一行搞定,更紧凑。
if i == j: continue,元素和自己配成"假对",导致配错晕倒。break 会整个终止循环,只应"跳过这一轮"时误用会漏掉大量组合。j(或 f2)而非又用 i,否则根本没在遍历第二个元素。len - 1;若写成 len 会比到最后一位,把本该不同的末字母也算进去。return [a, b] 打包成数组,只返回一个元素调用处取不到另一半。None,调用处要 if gemPair: 判断,否则对 None 取下标会报错。f1 != f2 and areTwins(...),别漏掉排除自比较的那一半。id + " " + id,漏了空格无法正确唤醒。恭喜你逃出墓地、唤醒了日月之力!这一单元你掌握了处理"成对关系"的利器——配对查找与嵌套循环。你理解了"对每个元素都要再遍历所有元素"的核心思想,学会了用外层 for i 套内层 for j 走遍所有两两组合、用 if i == j: continue(或内层从 i+1 起)跳过自比较;你既会用 value == value 做值匹配,也会用逐位比较到 len - 1 判断"除最后一位外相同"的双胞胎名字;还学会了把配对结果 return [a, b] 打包返回、用 if 结果: 判断成败。最重要的是,你体会到"配对框架不变、匹配条件可换"的抽象思维——同一套嵌套循环,既能配宝石又能配双胞胎。来回顾一下核心概念:
| 核心概念 | 关键点 | 对应关卡 |
|---|---|---|
| 配对查找 | 找出两个互相匹配的元素 | 两关 |
| 嵌套循环 | 外层 for i 套内层 for j | 两关 |
| 遍历所有组合 | 内层跑完一圈外层才前进 | 两关 |
| 跳过自比较 | if i == j: continue | 两关 |
| 省一半组合 | 内层从 i + 1 起 | 概览进阶 |
| 值匹配 | value == value | 无暇的宝石对 |
| 返回配对 | return [a, b] / None | 无暇的宝石对 |
| 字符串逐位比较 | 比到 len - 1,末位不计 | 孪生力量 |
| and 合并条件 | f1 != f2 and areTwins(...) | 孪生力量 |
| 框架复用 | 框架不变,换匹配条件 | 两关 |
懂得用嵌套循环遍历万千组合、又能精准跳过无效比较、找出天生一对的英雄,
才是云峰之上真正洞察关联的程序员。继续向雪山之巅挺进吧,勇敢的程序员!🏔️💎🌗
扣哒世界 编程知识 · 计算机科学 5(安息云峰):第十一单元
扣哒世界教研团队