ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

[Python]螺旋遍历,你真的懂了吗?——从一个备考生的真实提问说起

[Python]螺旋遍历,你真的懂了吗?——从一个备考生的真实提问说起 引子前两天一位正在备考华为OD的同学我们就叫他小玮吧给我发来了一段代码是他写的螺旋矩阵遍历。题目本身并不复杂给定一个m × n的矩阵按顺时针螺旋顺序返回所有元素。输入[[1,2,3],[4,5,6],[7,8,9]] 输出[1,2,3,6,9,8,7,4,5]小玮的代码思路是对的——按层模拟逐边遍历。但当我仔细看下去发现了一些典型的问题。这些问题不是他一个人独有的而是几乎所有刚开始接触螺旋遍历的人都会遇到的。于是我把他问我的几个问题整理出来配上详细的解答希望能帮到更多人。疑惑一range(right, left - 1, -1)会不会跑到-1这是小玮问我的第一个问题。他看到标准答案中遍历下边时用了这样的代码for col in range(right, left - 1, -1): result.append(matrix[bottom][col])他问“left如果是0的话left - 1不就是-1吗那range岂不是会遍历到-1”解答这个问题的核心在于理解 Python 中range的终止条件。range(start, stop, step)在step为负数时生成的序列从start开始每次递减step的绝对值直到小于等于​stop时停止注意stop本身不被包含。所以range(right, -1, -1)生成的序列是right, right-1, ..., 1, 0。它不会包含-1。因为当值为0时下一次递减会得到-1但-1不大于-1严格来说是等于而range的停止条件是“越过”stop所以循环停止在0。一个直观的验证for col in range(3, -1, -1): print(col, end ) # 输出3 2 1 0没有输出-1。为什么一定要写成left - 1而不是left如果写成range(right, left, -1)当left 0时生成的序列是right, right-1, ..., 1漏掉了0。为了包含left本身必须用left - 1。这是 Python 中反向遍历包含左端点的标准写法。疑惑二边界初始值到底该设多少小玮最初写的代码中边界是这样定义的top 0 bottom m # 错误 left 0 right n # 错误而正确的写法是top 0 bottom m - 1 left 0 right n - 1为什么是m - 1和n - 1因为矩阵的行索引是从0到m - 1列索引是从0到n - 1。bottom和right代表的是最后一行和最后一列的索引而不是行数和列数。如果设成m和n那么在第一次遍历时while top bottom and left right条件中0 m和0 n都成立循环进入。遍历上边时for col in range(left, right 1)会遍历到索引n但matrix[top][n]是越界的。一个简单的记忆方法行数、列数用m, n表示。边界索引用0, m-1, 0, n-1表示。凡是涉及“最大索引”的都要减1。疑惑三什么时候需要检查not matrix[0]小玮看到标准答案中有这样一行if not matrix or not matrix[0]: return []他问“not matrix我能理解是处理空列表。但not matrix[0]是什么情况”解答not matrix[0]是用来处理“矩阵存在但第一行为空”的情况。考虑以下几种输入输入not matrixnot matrix[0]结果[]True不会执行短路返回[][[]]FalseTrue返回[][[1,2]]FalseFalse正常遍历[[], [1,2]]FalseTrue返回[]如果不加not matrix[0]当输入为[[]]时m 1n 0。top, bottom 0, 0left, right 0, -1。while条件0 0 and 0 -1不成立循环不执行最终返回空列表[]。所以即使不加这个判断代码也不会报错最终结果也是正确的。但加上它是一个好习惯——防御性编程。它提前拦截了非法输入避免了后续可能出现的逻辑混乱比如right -1在更复杂的操作中可能引发意想不到的行为。疑惑四要不要检查每行长度是否一致小玮又问“如果矩阵中有一行比其他行长或短需要检查吗”解答在华为OD机考中不需要。题目描述通常会保证输入是合法的矩阵即所有行的长度相同。判分系统只会用合法输入来测试你的代码。额外检查行长度一致性的坏处浪费时间OD考试时间紧张不应该花在处理不存在的问题上。增加代码复杂度额外的检查会让代码变长增加出错的概率。不会加分判分系统只看合法输入下的输出是否正确。但在生产环境中如果你的代码要处理来自用户或外部系统的数据那么添加这样的检查是有意义的。这属于“防御性编程”的范畴。总结考试时不加工作中看情况加。疑惑五两种写法到底有什么区别这是小玮问得最深的一个问题。他看到了两种常见的螺旋遍历写法写法一机器人自走式row, col 0, 0 # 机器人的当前位置 while top bottom and left right: for col in range(left, right 1): result.append(matrix[top][col]) top 1 for row in range(top, bottom 1): result.append(matrix[row][right]) right - 1 # ...写法二边界收缩式while top bottom and left right: for col in range(left, right 1): result.append(matrix[top][col]) top 1 for row in range(top, bottom 1): result.append(matrix[row][right]) right - 1 # ...他问“这两个代码看起来几乎一样区别到底是什么”解答从代码层面看唯一的区别是写法一多定义了一对row, col变量而且在for循环中并没有真正用到它们因为for循环的临时变量已经隐式地充当了“脚步计数器”。但从思维模型层面看两者的区别非常大维度机器人自走式边界收缩式核心隐喻​一个机器人沿着墙边走一个画框不断向内缩小关注点​“我走到哪了”“范围还剩多少”核心变量​位置变量 边界变量只有边界变量变量关系​位置依赖边界边界也依赖位置边界独立临时变量用完即弃为什么这个区别很重要因为它对应着真实世界中两种完全不同的控制哲学机器人自走式适用于实体机器人的底层控制。扫地机器人需要知道自己当前的位置才能决定下一步往哪走。它的位置状态是持续跟踪的不能丢失。边界收缩式适用于多机器人调度或虚拟数据遍历。仓库管理员把货架区域划分为若干子区域分配给不同的机器人。管理员只关心“还有哪些区域没被盘点”不关心某个机器人此刻具体在哪。两种模式没有优劣之分它们是适用于不同场景的两种思维工具。总结从螺旋遍历中学到的回过头来看小玮的这几个问题其实反映了学习螺旋遍历过程中的几个关键坎理解range的终止条件——这是 Python 基础但容易忽略。边界初始值的设定——m-1和n-1是初学者最容易犯错的地方。防御性编程的尺度——什么时候该加检查什么时候不该加。从代码到思维的跃迁——两种写法看似相同背后的思维模型却截然不同。螺旋遍历这道题说难不难说简单也不简单。它的价值不在于让你记住一个固定的解法而在于逼迫你去思考边界、索引、循环控制这些编程的基本功。如果你也在刷这道题不妨问自己几个问题我的边界值设对了吗单行、单列的情况能正确处理吗我清楚自己用的是哪种思维模型吗想清楚了这些螺旋遍历就不再是一道需要“背答案”的题而是一个你已经真正理解的工具。本文首发于CSDN博客欢迎交流讨论。
返回列表