CC++ & Algorithm

当循环学会“搭配”,程序才真正开始像人一样思考

你有没有在厨房里同时照看三个灶台的经历?一个锅在炖汤,一个锅在炒菜,还有一个蒸锅在冒气。你不能只盯着一个锅看,得来回巡视,哪个需要翻动就翻哪个,哪个火候到了就关哪个。这种“同时管理多个任务、根据状态做不同决策”的能力,恰恰是循环综合应用的精髓。

很多人学完 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(微信同号)

这篇文章对你有帮助吗?

成为第一个评价的人

评论0

还没有评论,来抢沙发~

评论加载中...

想系统学习这个知识点?查看完整知识点 →