螺旋矩阵II:用循环不变量与左闭右开区间搞定边界

发布时间:2026/10/15 4:38:10

螺旋矩阵II:用循环不变量与左闭右开区间搞定边界 最近在重新过代码随想录的数组专题刷到 LC 59 螺旋矩阵 II 的时候我停下来认真想了一会儿。说实话这题标着中等难度但第一次动手写的人十个里有八个会在边界条件上翻车。题目要求非常直白给你一个正整数 n生成一个 n x n 的矩阵矩阵里的数字按顺时针螺旋的顺序依次填上从 1 到 n^2 的整数。它不像动态规划那样需要推导状态转移也不涉及什么高深数据结构纯粹就是考模拟能力考你能不能把一个重复性的过程用干净、不易错的代码写出来。我觉得这道题特别适合两类人一类是刚把数组基础过完、想练模拟题的初学者另一类是面试前想快速恢复边界处理手感的老手。下面我按自己刷题时的思路顺序把这题彻底拆开讲一遍。1. 先拆题螺旋矩阵 II 到底在问什么1.1 题面与可直接验证的例子先确认题目输入输出都很简单。输入一个正整数 n输出一个 n 行 n 列的二维数组数组内容是从 1 到 n^2 的连续整数并且按照“顺时针螺旋”的顺序排列。什么叫顺时针螺旋就是从外圈开始先从左到右走最上面一行再从上到下走最右边一列然后从右到左走最下面一行最后从下到上走最左边一列一圈完成之后再进入里面一圈继续同样操作。举两个例子。n 3 时输出应该是[ [1, 2, 3], [8, 9, 4], [7, 6, 5] ]n 4 时输出应该是[ [1, 2, 3, 4], [12, 13, 14, 5], [11, 16, 15, 6], [10, 9, 8, 7] ]为什么拿 n 4 也作为例子因为偶数矩阵不存在中心单点它正好能让你看到“圈”的边界到底怎么收缩和 n 3 的奇数情况互为补充。刷题的时候建议把 n 1、2、3、4、5 都脑内跑一遍很多边界问题会在这些例子里暴露出来。1.2 题目背后真正考察的点这道题表面上是一个二维数组填充题实际上考察的是三个非常基础的工程能力。第一循环不变量的掌控。也就是说你得明确告诉自己每条边走到哪个位置算结束是包含拐角点还是把拐角点留给下一条边如果不把这个问题想清楚代码里很容易出现重复覆盖或者漏掉某个格子。第二二维数组索引的熟练度。矩阵坐标写作matrix[row][col]行是第一个下标列是第二个下标。很多错误根本不是逻辑问题而是行和列写反了导致输出直接错乱。这类错误在本地跑的时候会很明显但在面试手写环节很隐蔽。第三特殊条件的处理。当 n 是奇数时矩阵最中心会单独剩下一个格子比如 n 3 的 9n 5 的 25。这个单点需要在循环结束后单独补上还是可以在循环里自然覆盖这是这题最容易埋雷的地方。另外这道题和 LC 54 螺旋矩阵是实打实的姐妹题。LC 54 是给你一个现成的矩阵让你按螺旋顺序输出所有元素LC 59 是反过来让你按螺旋顺序填入元素。两者的方向逻辑完全一致但一个是读一个是写边界处理上略有不同。我建议刷完 LC 59 后立刻去做 LC 54你会发现很多细节其实是互通的。2. 解题思路把矩阵看成一层层洋葱2.1 顺时针填充的宏观流程先别看代码在脑子里把整个过程想象成剥洋葱。最外层一圈的左上角是(0, 0)右下角是(n-1, n-1)。我们从左上角出发先沿着上边从左往右填到了右上角后转弯沿着右边从上往下填到了右下角后转弯沿着下边从右往左填到了左下角后再转弯沿着左边从下往上填最终回到左上角附近正好把外圈填满。外圈填完后下一圈就相当于一个规模缩小后的新矩阵。此时新的左上角变成(1, 1)新的右下角变成(n-2, n-2)也就是四个边界各往内收缩一格。这个过程重复进行直到所有格子都被填满。那到底要循环多少圈看 n 的一半。n 4 时外圈填完后内圈是一个 2 x 2 的小矩阵所以一共两圈n 5 时外圈、第二圈然后最中心剩下一个单点所以其实也是两圈加一个点。用数学语言来说圈数约等于n // 2奇数时最后会多出一个中心点。这个宏观流程非常简单难点全在“每条边具体走几步”。2.2 方向数组解法为什么不够香有人会想到用“方向数组”来做定义四个方向向量比如右、下、左、上分别是(0, 1)、(1, 0)、(0, -1)、(-1, 0)再用一个 visited 布尔矩阵记录哪些格子已经填过。每走一步先判断下一个位置是否越界或者已经被访问过如果是就转向。这个思路怎么说呢很通用什么形状的矩阵都能套但代码写出来会比按圈模拟要长。而且每次步进都要做边界判断还要额外维护一个 visited 数组面试现场写起来很容易中途乱掉。我个人觉得LC 59 生成的是正方形矩阵结构足够规整完全可以用更精准的“按圈模拟”来做。方向数组不是不能写只是在这个题上有点杀鸡用牛刀还容易给自己增加包袱。2.3 别惦记数学公式直填网上偶尔能看到一种做法不模拟而是直接通过某个坐标值计算它应该填什么数字。比如用当前位置到矩阵边界的最短距离来判断它在第几圈再结合方向套一个公式。这种解法看起来特别聪明但它有两个问题第一公式推导成本高面试时除非你事先背过否则现场推非常容易卡壳第二就算推导出来也很难向面试官展示你对边界控制的理解。刷算法题不能只追求炫技LC 59 的价值恰恰在于让你练习模拟和循环不变量。老老实实用模拟法是最稳妥、也最容易说服人的答案。3. 核心技巧循环不变量让四条边不重不漏3.1 到底啥是左闭右开我刷代码随想录数组专题的时候对这个概念印象很深写循环之前先想清楚每一次循环的区间到底是“左闭右开”还是“闭区间”然后在整段逻辑里保持一致。放到 LC 59 里具体做法是每一圈的四条边都采用“左闭右开”的区间。什么意思每条边只包含起点不包含终点把终点留给下一条边作为起点。假设当前圈的左上角是(start, start)右下角是(end, end)定义length end - start这个length表示当前圈每条边需要填充多少个格子。注意不是end - start 1因为我们故意不把每条边的最后一个格子算进来。把它画成坐标表达式就是上边行固定为start列从start到start length - 1右边列固定为end行从start到start length - 1下边行固定为end列从end到end - length 1左边列固定为start行从end到start 1这个“每条边长度完全相等”的设计非常优雅。四条边的长度加起来正好是4 * length不会多填一个拐角也不会漏掉一个格子。为什么强调循环不变量因为只要你始终遵循同一个区间定义即使矩阵再大、圈数再多处理逻辑也不会走形。3.2 一圈结束后的收缩规则一圈填完后需要把边界向内收缩。具体操作就是start 1 end - 1因为本次循环把最外圈填完了下一圈的范围自然要往中间靠。循环的终止条件用while start end。这里的start end意味着当前还有至少一条边可以填充也就是还存在一个“圈”。我来对照几个例子验证一下n 3 时一开始start 0end 2满足start end进入循环length 2四条边各填 2 个格子刚好填满外圈的 8 个数字。之后start 1end 1此时start end不成立循环退出。因为 3 是奇数最中心还有一个格子再用一行if start end单独填上数字 9。n 4 时第一轮start 0end 3length 3外圈填 12 个数字第二轮start 1end 2length 1四条边各填 1 个格子一共 4 个数字正好把内部 2 x 2 的方块填满。之后start 2end 1循环退出而且start ! end不需要单独处理中心点。完美。n 5 时第一轮length 4第二轮length 2之后start 2end 2循环退出用if start end填中心点 25。这个过程无论奇数偶数都自然成立。3.3 为什么下边和左边要倒着填这里是我第一次写代码摔得最惨的地方。上边和右边是顺着走下边和左边必须倒着走否则会覆盖已经填好的区域。用一个生活类比。想象你在操场上绕圈跑步从左上角出发沿上边向右跑到了右上角沿右边向下跑到了右下角沿下边向左跑到了左下角沿左边向上跑。顺序固定所以下边一定是从右往左左边一定是从下往上。在代码里下边如果还照着“从左往右”的思维来写比如matrix[end][start i]那就会从这一行的最左端开始覆盖把之前右边已经填过的格子或者未来应该保留的位置全部弄乱。正确写法是matrix[end][end - i]左边同理不能用matrix[start i][start]而应该用matrix[end - i][start]我自己的经验是写完四个 for 循环后立刻把两个反向循环单独检查一遍确认用的变量是end - i而不是start i。这个细节比逻辑本身更容易出错尤其是手写代码的时候脑子里稍微分个神就可能错位。4. 代码落地这种写法最容易一次写对4.1 Python 参考实现按照上面的思路我直接给出我最喜欢写的一版 Python 代码。它没有多余的状态变量四个 for 循环对应四条边结构非常清晰。def generateMatrix(n: int) - list[list[int]]: matrix [[0] * n for _ in range(n)] start, end 0, n - 1 num 1 while start end: length end - start # 上边行固定 start列从左到右 for i in range(length): matrix[start][start i] num num 1 # 右边列固定 end行从上到下 for i in range(length): matrix[start i][end] num num 1 # 下边行固定 end列从右到左 for i in range(length): matrix[end][end - i] num num 1 # 左边列固定 start行从下到上 for i in range(length): matrix[end - i][start] num num 1 start 1 end - 1 # 奇数 n 时最中间还剩一个格子 if start end: matrix[start][start] num return matrix这段代码的关键点在于length end - start。它不是边长而是边长减一也就是每条边实际需要填充的格子数量。所有四个 for 循环都使用同一个length所以四条边天然等长。num从 1 开始每填一个格子自增一次循环结束后刚好停在n^2 1中心点补上时就是最后一个数字。你可以用 n 3 在纸上走一遍第一轮length 2上边填(0,0)1、(0,1)2右边填(0,2)3、(1,2)4下边填(2,2)5、(2,1)6左边填(2,0)7、(1,0)8。第二轮start end 1退出循环补(1,1)9。输出和题目示例完全一致。4.2 另一种常见写法用 offset 控制边界代码随想录的题解里还流行一种用offset的写法核心思想是每圈把右边和下边的边界向里多缩一格。大致结构是这样的loop n // 2 start 0 offset 1 while loop: # 上边j 从 start 到 n - offset - 1 # 右边i 从 start 到 n - offset - 1 # 下边j 从 n - offset - 1 到 start步长 -1 # 左边i 从 n - offset - 1 到 start步长 -1 start 1 offset 1 loop - 1 if n % 2 1: matrix[start][start] num这种写法本身没有任何问题很多老手也喜欢。它比我上面的版本多用了一个loop变量来记忆剩余圈数并用n - offset来动态计算当前圈右下边界。使用它的好处是更贴近代码随想录原版题解的叙事网上资料多坏处是新手容易混淆offset的含义为什么第一圈offset是 1不是 0如果没想清楚代码按着记忆写就很容易越界。我个人更推荐start/end直接表示左右下边界的方式因为变量含义直观少做一层换算。你只要记住每一轮结束后start、end--就行。4.3 复杂度分析LC 59 的复杂度其实是送分题但面试的时候经常被追问所以我单独讲一下。矩阵一共有 n 行 n 列也就是n^2个格子。无论模拟过程写得多花哨每个格子最终都只会被填一次所以总操作次数是O(n^2)。空间上除了返回的矩阵本身我们只用了start、end、num、length等几个整型变量额外空间是O(1)。有人会较真返回矩阵本身不也占用了O(n^2)空间标准算法分析在讨论“额外空间”时通常不把必要的输出空间计入所以写O(1)没问题。但如果你在面试时主动补充一句“不算返回矩阵的话额外空间是常数级”反而显得你思路严谨。时间复杂度已经不可能更优了因为题目要求的输出本身就有n^2个数你连输出都必须花这么多时间。5. 边界条件与高频报错现场5.1 最容易翻车的五个细节我把刷这题时见过、踩过的高频错误整理成一个表方便你对照自查。细节错误表现正确做法循环条件写错用while start end以为等号能处理中心点结果四边长度为 0中心漏填用while start end中心点靠if start end补每条边多走一格for 循环写成range(length 1)把下一条边的起点也填了严格使用range(length)保持左闭右开下边方向写错写成matrix[end][start i]覆盖已填行写成matrix[end][end - i]左边方向写错写成matrix[start i][start]覆盖已填列写成matrix[end - i][start]忘记收缩边界四个 for 填完后没有start 1和end - 1死循环或重复填同一圈每轮结束必须向内收缩第一个坑要重点说。很多人看到奇数 n 要补中心点就下意识把循环条件写成while start end期待中心点自动处理。但麻烦在于当start end时length 0四个 for 循环一次都不会执行中心点依旧空着。除非在length 0时额外判断否则还是会漏。与其多一个分支不如就用while start end把中心点统一丢给后面的if处理逻辑更统一。5.2 自测用例与验证方法代码写完之后不要急着提交先在本地跑几个典型用例。n 1应该得到[[1]]。很多边界方案在这会直接越界用while start end的话进不了循环然后if start end补中心点安全通过。n 2应该得到[ [1,2], [4,3] ]。此时第一轮length 1四条边各填一个点正好填满 2 x 2 矩阵。n 3题目标准示例跑一遍就能发现中心点有没有漏。n 4重点验证第二轮length 1时四个点是否能形成内部 2 x 2 的右、下、左、上顺序。n 5既验证多圈又验证奇数中心点 25。如果想要程序化验证可以写一个简单检查把结果矩阵按顺时针螺旋读一遍看是否得到[1, 2, ..., n^2]。这其实就是 LC 54 的实现两道题刚好互相印证。5.3 面试现场如何安排答题节奏我总结了一套适合面试现场使用的答题顺序能明显降低 bug 率。先不要急着写代码。开口先说思路这题可以用模拟法把矩阵分成从外到内的一圈一圈每一圈四条边都采用左闭右开区间每条边长度相等填完之后向内收缩边界。面试官听到这里基本就能确认你思路没走偏。然后定义变量start是当前圈左上角end是当前圈右下角num是当前要填的数字。每填一条边num自增。接着按上边、右边、下边、左边的顺序写四个 for 循环。写完代码后主动拿 n 3 口头跑一遍。这一步非常加分即使代码有一点小错你也有机会边说边发现。我刷题那会儿靠这个习惯挽救过至少三次手误。如果面试官问你有没有其他写法你可以提方向数组加 visited但强调在正方形矩阵里按圈模拟的代码更短、边界更可控。只要解释得有逻辑面试官一般不会为难。6. 从这道题延伸开去相关变体与训练方法6.1 LC54 螺旋矩阵同一个套路反过来用LC 54 给一个 m x n 矩阵要求按顺时针螺旋顺序输出所有元素。它和 LC 59 的区别在于一个是读数字一个是写数字一个是任意矩形一个是正方形。LC 54 用“上下左右四个边界收缩”的写法更通用伪代码大概是这样top, bottom, left, right 0, m-1, 0, n-1 res [] while top bottom and left right: # 从左到右遍历 top 行 # 从上到下遍历 right 列 # 如果 top bottom从右到左遍历 bottom 行 # 如果 left right从下到上遍历 left 列 top 1 bottom - 1 left 1 right - 1注意其中两个 if 判断因为矩形不一定是正方形如果行数或列数不对称最后可能只剩一行或一列。LC 59 因为输入是正方形这个麻烦小很多这也是我建议先做 59 再做 54 的原因。6.2 更多二维矩阵模拟变体除了 LC 54还有几个常见变体也值得一练。逆时针螺旋填充。这个最简单只要把方向顺序从“右、下、左、上”改一下变成“下、右、上、左”代码骨架不用动。关键是四个 for 的填充起点和方向要重新画一下不能硬套。从中心点开始顺时针向外填。这个就很有挑战了不再是“一圈一圈向内收缩”而是“一圈一圈向外扩张”。一个常见实现是先预分配矩阵然后从中心点(n//2, n//2)开始按步长 1、1、2、2、3、3 扩张每两步换一个方向。这个变体更适合用来巩固方向数组写法和 LC 59 正好互补。蛇形填充。就是按“之”字形从上到下再从左到右来回填不涉及螺旋但同样是二维数组模拟题可以用来锻炼状态切换能力。6.3 如何把这类题纳入自己的题库刷题最忌讳的是孤立地背答案。我建议你在笔记里给 LC 59 打一个标签二维数组模拟 / 边界控制然后把它和 LC 54、LC 48 旋转图像、LC 73 矩阵置零放在一起形成一个二维数组模拟的专题。每次刷完一类题回头总结一下它们的共同点是不是都在强调循环不变量是不是都在处理边界收缩。代码随想录的数组专题也很有意思它把 LC 59 放在数组部分靠后前面是二分查找、移除元素、移动窗口等题目。你不妨顺着这个顺序刷一遍会发现“区间定义一致性”这个思想在二分、滑动窗口、螺旋矩阵里反复出现。如果做 LC 59 时觉得很吃力不用急着死磕回去再想想二分查找里的while left right和取中位数的边界很多感觉是相通的。我个人特别喜欢这道题因为它的代码量不大却能炸出一堆细节问题。我第一次写出可运行版本的时候就是用start/end的四层 for 循环当时边写边在纸上画 n 4 的圈走完一遍之后才真正理解了什么叫开区间边界。到现在我每次面试前还会拿 LC 59 做热身找张纸画出 n 5 的填充轨迹确保自己对end - i这种反向索引还有肌肉记忆。如果你也正在刷题别背代码多画几遍让手指和脑子同时对边界敏感这比闷头刷十道重复的模拟题都管用。
延伸阅读

更多相关文章

2026/10/15 4:38:10

Spring Boot宠物医院管理系统:从数据库设计到部署实战解析

1. 项目概述1.1 这是什么项目,解决什么问题一份宠物医院管理系统的毕业设计源码,技术栈锁定在 Spring Boot。做过毕设的都知道,管理系统类题目是整个毕业设计题库里最稳的选择——需求明确、模块清晰、工作量可控,答辩的时候也容易…

2026/10/15 4:38:10

从曲面细分到计算着色器:我的 Vulkan 实验记录

一、曲面细分(Tessellation)做了什么给鸭子模型加了一条细分管线。细分是在渲染管线里插两个新阶段:细分控制着色器(TCS,tessellation control)和细分求值着色器(TES,tessellation e…

2026/10/15 4:38:10

AI应用开发工程师是2026年最紧缺的技术岗位之一

AI应用开发工程师是2026年最紧缺的技术岗位之一。与传统算法工程师不同,这个角色不需要从零训练模型,而是专注于将大语言模型(LLM)能力与业务场景深度结合,构建可落地的AI产品。核心定位:以Python为主要语言…

2026/10/15 5:43:12

Agent开发省钱指南:少踩坑比省Token更值得投入

1. 为什么“少踩坑”比“省Token”更值得投入做过Agent项目的人大概都有过这种体验:上线前反复压缩Prompt,把系统提示词从两千字砍到八百字,把few-shot示例从十个减到三个,看着后台的Token消耗曲线往下掉,心里挺美。结…

2026/10/15 5:43:12

2026 Java面试高频考点速查清单:集合、并发、JVM、Spring全覆盖

2026年了,Java面试还是躲不开八股。与其抱怨“面试造火箭、工作拧螺丝”,不如换个角度想:八股题考察的恰恰是知识体系的完整性和深度,而这两点在大型项目里直接决定了你排查问题的上限。我自己近两年帮团队做技术面试,…

2026/10/15 5:43:12

考研分数线预测与院校推荐系统:Django+Vue全栈实现指南

做计算机毕业设计这些年,见过太多同学一上来就急着找代码、跑项目,结果一到答辩就被问得哑口无言。考研分数线预测与院校推荐这个题目,是这几年大数据方向里少有的“数据价值能讲清楚”的选题,同时难度适中,适合本科阶…

2026/10/15 5:43:12

PyTorch从入门到精通:动态图、自动求导与显存管理实战

1. 为什么“pytorch”这个标题值得单独拎出来聊如果你在技术社区里闲逛,看到有人只甩出“pytorch”这一个词当标题,大概率会以为这是一篇入门安装教程。但我第一眼看到这个标题时,脑子里冒出来的却是另一幅画面:一个已经跑通了几个…

2026/10/15 5:43:12

组合模式实战:用一致接口优雅处理树形层级数据

设计模式里有一类很特别的结构型模式,它看起来不起眼,但一旦遇到"树形层级数据",它几乎就是最优雅的解法——这就是组合模式。组合模式的核心思想用一句话讲:把对象组织成树形结构,表示"部分-整体"…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/14 17:41:07

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/15 0:02:22

防关联浏览器哪个好?三款产品实测差异解析

新手做多店铺,最纠结的就是防关联浏览器选哪个。飞跨、网易、站斧这三款被问得最多,先给结论:要一体化省心、长期便宜,选站斧;要网易招牌和稳,选网易;预算紧做东南亚 Lazada,选飞跨。…

2026/10/15 0:02:22

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成 一、技术背景 大模型(LLM)在通用场景下表现强大,但当我们将 GPT-4 或 Qwen 直接用于生成影刀RPA操作指令和 XPath 定位时,准确率通常只有 40%-60%。原因…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑