当循环学会“搭配”,程序才真正开始像人一样思考
你有没有在厨房里同时照看三个灶台的经历?一个锅在炖汤,一个锅在炒菜,还有一个蒸锅在冒气。你不能只盯着一个锅看,得来回巡视,哪个需要翻动就翻哪个,哪个火候到了就关哪个。这种“同时管理多个任务、根据状态做不同决策”的能力,恰恰是循环综合应用的精髓。
很多人学完 for 循环和 if-else 之后,单独用都没问题,但一旦要把它们组合起来解决实际问题,就不知道从哪下手了。这就像你学会了切菜、学会了调味、学会了控制火候,但面对一桌食材,还是不知道怎么做出一顿完整的饭。
今天我们就来聊聊,循环和分支的“搭配艺术”。
循环加判断,本质是在做“分类决策”
先看一个最朴素的场景:老师手头有一摞成绩单,想统计每个等级有多少人。
你当然可以写五个变量,count_a、count_b、count_c……但稍微有点经验的开发者会立刻想到用字典。为什么?因为字典的本质是“键值映射”,而“等级→人数”天然就是一组映射关系。
count = {"A":0, "B":0, "C":0, "D":0, "E":0}
for score in scores:
if score >= 90:
count["A"] += 1
elif score >= 80:
count["B"] += 1
# ...
这段代码的核心逻辑其实就两步:循环负责“遍历每一个”,分支负责“判断属于哪一类”。把它们拆开看都很简单,但组合在一起,就完成了一个“自动分类计数器”。
这里有一个容易被忽视的细节:判断顺序必须是从高到低。如果你先判断 score >= 80,那90分也会被归到B等。这就像安检一样,你得先过最严格的那道关卡,才能保证不漏掉任何异常。顺序本身就是逻辑的一部分。
循环的“隐藏技能”:for-else
Python 里有一个很多语言没有的特性——for 循环可以跟一个 else 块。
for attempt in range(3):
user_input = input("请输入密码:")
if user_input == password:
print("欢迎进入系统!")
break
else:
print("尝试次数已用完,程序退出。")
这个 else 的执行条件是:循环没有被 break 中断。换句话说,如果三次都输错了,循环“正常走完”,else 才会执行。
你可以把它理解成一个“善后机制”——如果任务成功完成了(break),就不需要善后;如果任务失败了(循环耗尽),就执行善后逻辑。这个设计在密码验证、查找元素、重试机制等场景中特别优雅,省去了额外的标志变量。
多重赋值:一行代码背后的思维跃迁
斐波那契数列是讲循环时绕不开的经典例子。但真正值得关注的不是数列本身,而是这行代码:
a, b = b, a + b
很多人第一次看到会愣一下:这不就是同时更新两个变量吗?有什么特别的?
特别之处在于右边先全部求值,再同时赋给左边。如果你拆成两行写:
a = b
b = a + b # 这里的 a 已经被覆盖了!
结果就完全错了。这就像接力赛中交接棒——你必须先把棒递出去,再跑下一个位置,而不是先跑到下一个位置再回头拿棒。
这个细节告诉我们:循环体中语句的执行顺序,有时候比语句本身更重要。写循环的时候,脑子里要有一条清晰的时间线,知道每一步执行时变量的值是什么。
嵌套循环:维度的叠加
单个循环处理的是一维问题——遍历一个列表、重复n次操作。但现实中很多问题是二维甚至多维的。
比如打印九九乘法表,外层循环控制“第几行”,内层循环控制“这一行有几个算式”。总执行次数是两层循环次数的乘积——外层9次,内层分别执行1到9次,总共45次。
for i in range(1, 10):
for j in range(1, i + 1):
print(f"{j}×{i}={i*j:2d}", end=" ")
print()
这里内层循环的终止条件是 i + 1 而不是固定的10,这就是嵌套循环的灵活性所在:内层的范围可以依赖外层的当前状态。
但要注意,break 只能跳出当前所在的那一层循环。如果你在内层 break 了,外层循环该继续还是继续。想跳出所有层,要么用标志变量,要么重新设计逻辑。
综合实战:当循环遇到大规模数据
来看一道更有挑战性的题目。
有 n 种魔法材料和 m 种魔法符文,第 i 种材料蕴含 a_i 点魔力,第 j 种符文蕴含 b_j 点魔力。每次融合选择一种材料+一种符文,产生的魔力值为 min(a_i + b_j, p)。求所有可能融合的魔力值之和。
如果你用两层循环暴力枚举所有组合,时间复杂度是 O(n×m)。当 n 和 m 都达到 2×10^5 时,这个方案直接超时。
这道题的关键在于:min(a_i + b_j, p) 的结果只有两种可能——要么是 a_i + b_j(没超过阈值),要么就是 p(超过了阈值)。
所以我们可以换个角度:
- 对所有材料排序,对所有符文排序
- 对于每个 a_i,用二分查找找到满足 a_i + b_j < p 的最大 b_j 位置
- 这些“没溢出”的组合贡献的是 a_i × k + 这些 b_j 的和(用前缀和快速计算)
- 剩下的组合全部贡献 p
这样就把 O(n×m) 降到了 O((n+m) log(n+m))。
# 核心思路(伪代码)
for each a_i:
k = 二分找到 a_i + b_j < p 的边界
total += a_i * k + prefix_sum_b[k]
total += (m - k) * p
这道题看起来是循环的题目,但真正的考点是如何避免不必要的循环。当你发现内层循环做的事情可以用数学方法批量处理时,就该考虑优化了。循环是工具,不是目的。
从“会写循环”到“会用循环”
回到开头那个厨房的比喻。新手厨师可能手忙脚乱,不知道该先管哪个锅。而有经验的厨师知道:炖汤可以放着不管,炒菜需要持续翻炒,蒸锅到时间了会有提示。不同的任务需要不同的关注策略。
写循环也是一样:
- 遍历列表用
for,因为你知道要处理每一个元素 - 不确定循环几次用
while,因为你依赖某个条件来决定是否继续 - 需要提前退出用
break,因为继续下去没有意义 - 需要跳过某些元素用
continue,因为当前这个不需要处理 - 循环正常结束后的收尾工作,可以用
for-else
这些工具单独拿出来都不难,难的是在面对一个具体问题时,能快速判断该用哪个、怎么组合。这种判断力不是看几篇文章就能获得的,得靠一道一道题去练、去踩坑、去反思。
建议你从今天开始,每遇到一个需要重复处理的场景,就想想能不能用循环自动化。哪怕只是批量重命名文件、批量处理数据,都值得动手写一写。代码写得越多,这些“综合应用”就会变得像呼吸一样自然。
关于作者
我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。
这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。
如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)