首页 / 资讯中心 / 文章详情

蛇形矩阵生成算法详解:从模拟思路到代码实现

蛇形矩阵生成算法详解:从模拟思路到代码实现 ★ FEATURED ARTICLE
1. 蛇形矩阵到底是什么为什么值得单独拎出来讲蛇形矩阵Snake Matrix是二维数组填充类问题里非常经典的一种变体。它和螺旋矩阵、对角线遍历矩阵属于同一个家族但规则更简单、更容易上手同时又能很好地训练对边界控制和方向切换的理解。我第一次接触这个题是在准备校招笔试的时候当时觉得“不就是按顺序填数嘛”结果真上手写才发现方向切换的时机、行列索引的更新顺序、边界条件的判断每一个细节都能让你调试半天。先把问题说清楚。蛇形矩阵要解决的问题是给定一个正整数 n生成一个 n×n 的方阵把 1 到 n² 这些数字按照“蛇形”的路径依次填入。所谓蛇形就是第一行从左到右填第二行从右到左填第三行再从左到右如此交替往复像一条蛇在矩阵里来回游走。最终输出的就是一个每行数字连续、但方向交替的方阵。这个题看起来简单但它能考察的东西其实不少对二维数组索引的熟练度、对奇偶行处理的敏感度、对循环边界的把控能力。而且它有很多变体比如斜向蛇形、从外圈往内圈的蛇形、Z字形遍历等等。掌握基础版本之后这些变体基本都能触类旁通。这篇文章适合谁看如果你是刚开始刷算法题的新手蛇形矩阵是一个非常好的练手题比螺旋矩阵简单但比单纯的矩阵转置有意思得多。如果你已经有一定基础这篇文章里关于边界处理、方向切换的几种写法对比以及我踩过的那些坑应该也能给你一些参考。下面我会从整体思路、核心细节、完整实现、常见问题几个维度把这个题彻底讲透。2. 整体思路拆解为什么大多数人第一反应是“模拟”2.1 模拟法为什么是最自然的选择拿到这个题绝大多数人的第一反应就是“模拟”——按照题目描述的规则一步一步地把数字填进去。这个思路非常自然因为蛇形矩阵的生成过程本身就是确定性的你知道每一步该往哪个方向走知道什么时候该换行知道什么时候该换方向。模拟法就是把这个过程用代码翻译一遍。模拟法的核心逻辑可以概括为逐行处理奇数行从左到右偶数行从右到左。这里说的“奇数行”“偶数行”是从 0 开始计数的行索引还是从 1 开始计数的行号会影响判断条件后面会详细说。每一行内部用一个循环把当前该填的数字依次放进对应的列位置。为什么模拟法是最优解因为这个题没有更“聪明”的数学公式可以直接算出某个位置的值——至少对于基础蛇形矩阵来说找通项公式的复杂度远高于直接模拟。而且模拟法的时间复杂度是 O(n²)空间复杂度也是 O(n²)因为要存储结果矩阵这已经是最优了因为你至少要把 n² 个数字都写一遍。2.2 方向切换的两种理解方式蛇形矩阵的方向切换有两种理解方式对应两种不同的代码写法。第一种是按行切换把矩阵看成 n 行逐行填充。第 0 行从左到右第 1 行从右到左第 2 行从左到右……判断依据就是行索引的奇偶性。这种写法最直观代码也最简洁适合新手理解。第二种是按方向切换把整个过程看成“向右走”和“向左走”两种状态的交替。每走完一行就切换一次方向同时行号加一。这种写法更接近“蛇”的隐喻但在实现上其实和第一种是等价的只是组织代码的方式不同。我个人的建议是新手先用第一种写法把题做出来理解清楚之后再尝试第二种。第一种写法能帮你快速建立信心第二种写法能帮你理解状态机的思想对后续做更复杂的矩阵题有帮助。2.3 边界条件的核心什么时候换行什么时候换方向蛇形矩阵的边界条件其实只有两个当前行填满了没有以及当前方向是什么。每填完一行行号加一方向取反。听起来很简单但实际写代码的时候最容易出错的地方恰恰是“填完一行”的判断。举个例子假设 n3第一行要填 1、2、3填完之后行号从 0 变成 1方向从“从左到右”变成“从右到左”。第二行要填 6、5、4注意这里数字是递减的因为方向反了。填完之后行号变成 2方向再变回“从左到右”。第三行填 7、8、9。如果你用两层循环来写外层循环控制行内层循环控制列那么内层循环的起始值和终止值需要根据方向来动态设置。从左到右时列从 0 到 n-1从右到左时列从 n-1 到 0。这个逻辑本身不难但如果你用 while 循环手动控制索引就很容易在边界上多走一步或者少走一步。提示写模拟法的时候建议先用纸笔把 n3 和 n4 两种情况手动推一遍把每一步的行号、列号、当前数字都写出来。这个过程看起来笨但能帮你提前发现很多边界问题。3. 核心细节解析从伪代码到可运行程序的几个关键决策3.1 数据结构选择二维数组还是列表嵌套在大多数编程语言里生成蛇形矩阵最直接的数据结构就是二维数组。以 Python 为例你可以用列表嵌套列表来表示也可以用 NumPy 数组。但在算法题场景下通常不允许引入第三方库所以列表嵌套列表是最常见的选择。初始化的时候有一个小坑如果你用[[0] * n] * n这种方式创建二维数组会得到一个“浅拷贝”的结果——所有行都指向同一个列表对象。修改其中一行其他行也会跟着变。正确的写法是[[0] * n for _ in range(n)]用列表推导式确保每一行都是独立的对象。这个坑我在第一次写的时候踩过当时调试了半天才发现问题出在初始化上。后来养成了习惯只要创建二维数组一律用列表推导式绝不用乘法。3.2 行索引与方向的对应关系前面提到方向切换的依据是行索引的奇偶性。这里有一个容易混淆的地方行索引是从 0 开始还是从 1 开始。如果你用for i in range(n)来遍历行那么 i 的取值是 0、1、2、…、n-1。此时“奇数行”对应的是 i 为奇数的行也就是第 2 行、第 4 行从 1 开始计数。判断条件写成if i % 2 0表示从左到右else表示从右到左。如果你习惯从 1 开始计数那就用for i in range(1, n1)此时判断条件变成if i % 2 1表示从左到右。两种写法都对关键是在整个代码中保持一致不要一会儿用 0 基一会儿用 1 基否则必出 bug。我个人的习惯是用 0 基索引因为大多数编程语言的数组都是 0 基的保持一致可以减少心智负担。3.3 数字递增与列索引的关系蛇形矩阵的填充过程中数字是始终递增的从 1 到 n²。但列索引的变化方向取决于当前行的方向。从左到右时列索引递增数字也递增从右到左时列索引递减但数字仍然递增。这意味着你不能简单地用“列索引 1”来作为填入的数字。正确的做法是维护一个独立的计数器num每填一个位置就num 1无论列索引是递增还是递减。这个细节看起来微不足道但如果你试图用列索引来推导数字就会在反向行上得到错误的结果。我见过有人试图用公式i * n j 1来计算每个位置的值这个公式只适用于从左到右的行反向行需要另外处理反而把问题复杂化了。老老实实用计数器是最稳妥的。3.4 循环写法的选择for 还是 while对于每一行的填充你可以用 for 循环也可以用 while 循环。for 循环的写法更简洁但需要根据方向动态设置 range 的起始值和步长。比如从左到右时for j in range(n)从右到左时for j in range(n-1, -1, -1)。这种写法可读性好但需要写两个分支。while 循环的写法更灵活用一个变量控制列索引根据方向决定是加一还是减一同时判断是否越界。这种写法代码量稍多但逻辑更统一适合后续扩展到更复杂的蛇形变体。我两种写法都写过实测下来对于基础蛇形矩阵for 循环的双分支写法更不容易出错因为 range 的边界是显式指定的不需要手动判断越界。while 循环虽然灵活但容易在边界上多走一步或少走一步。4. 完整实现与逐行解析一份可以直接抄的代码4.1 Python 版本的完整实现下面这份代码是我经过多次调试后整理出来的版本逻辑清晰边界处理稳妥可以直接拿去用。def snake_matrix(n): # 初始化 n x n 的二维数组每个位置先填 0 matrix [[0] * n for _ in range(n)] # 当前要填入的数字从 1 开始 num 1 # 逐行填充 for i in range(n): if i % 2 0: # 偶数行0基从左到右 for j in range(n): matrix[i][j] num num 1 else: # 奇数行0基从右到左 for j in range(n - 1, -1, -1): matrix[i][j] num num 1 return matrix # 测试 result snake_matrix(4) for row in result: print(row)运行结果应该是[1, 2, 3, 4] [8, 7, 6, 5] [9, 10, 11, 12] [16, 15, 14, 13]这份代码的核心逻辑非常直白外层循环遍历每一行内层循环根据行号的奇偶性决定填充方向。计数器num独立维护确保数字始终递增。4.2 关键步骤的逐行拆解第一行matrix [[0] * n for _ in range(n)]创建了一个 n×n 的矩阵所有元素初始化为 0。这里用列表推导式而不是乘法原因前面已经说过是为了避免浅拷贝问题。num 1是填充计数器从 1 开始因为蛇形矩阵的数字范围是 1 到 n²。外层for i in range(n)遍历每一行。i从 0 到 n-1对应矩阵的第 1 行到第 n 行。if i % 2 0判断当前行是否为偶数行0基。如果是偶数行执行从左到右的填充for j in range(n)j从 0 到 n-1依次填入num然后num加一。else分支处理奇数行执行从右到左的填充for j in range(n-1, -1, -1)j从 n-1 递减到 0依次填入num然后num加一。最后返回填充好的矩阵。4.3 其他语言的等价实现如果你用的是 Java 或 C逻辑完全一样只是语法不同。Java 版本大概长这样public static int[][] snakeMatrix(int n) { int[][] matrix new int[n][n]; int num 1; for (int i 0; i n; i) { if (i % 2 0) { for (int j 0; j n; j) { matrix[i][j] num; } } else { for (int j n - 1; j 0; j--) { matrix[i][j] num; } } } return matrix; }C 版本也类似用vectorvectorint来存储矩阵。核心逻辑完全一致说明这个思路是跨语言通用的。4.4 复杂度分析与优化空间时间复杂度是 O(n²)因为矩阵有 n² 个位置每个位置填一次。空间复杂度也是 O(n²)因为要存储整个矩阵。这两个复杂度都已经是最优了没有进一步优化的空间——你不可能在不遍历所有位置的情况下填满矩阵。如果面试官追问“能不能优化空间”你可以说如果只是要求按顺序输出数字而不需要存储矩阵那空间复杂度可以降到 O(1)因为每一行的数字范围是可以直接计算出来的。但题目通常要求返回矩阵所以 O(n²) 是必须的。5. 常见问题与排查技巧实录5.1 为什么我的输出总是错位错位是蛇形矩阵最常见的 bug表现为某些行的数字顺序不对或者数字重复、缺失。根据我的调试经验错位通常来自以下几个原因第一个原因是方向判断条件写反了。比如把i % 2 0写成了i % 2 1导致第一行就从右到左填整个矩阵的方向全部颠倒。这种错误很容易发现因为输出结果的第一行就是反的。第二个原因是列索引的起始值或终止值写错了。比如从右到左时写成了range(n-1, 0, -1)漏掉了列索引为 0 的位置导致每行的第一个数字缺失。正确的写法是range(n-1, -1, -1)终止值必须是 -1 才能包含 0。第三个原因是计数器更新时机不对。比如在判断方向之前就更新了num导致数字和位置错位。正确的做法是在确定位置之后再填入num然后立即更新num。5.2 边界情况n1 和 n0 怎么处理n1 时矩阵只有一个元素应该输出[[1]]。用上面的代码测试外层循环只执行一次i0是偶数行内层循环j从 0 到 0填入 1返回[[1]]结果正确。n0 时理论上应该返回空矩阵。但大多数题目会限定 n 为正整数所以不需要特别处理。如果你想让代码更健壮可以在函数开头加一个判断if n 0: return []。还有一个容易被忽略的边界情况是 n 为负数。虽然题目通常不会这么出但在实际工程中输入校验是必要的。加一行if n 0的判断成本很低但能避免很多潜在问题。5.3 常见问题速查表问题现象可能原因排查方法解决方案第一行方向反了奇偶判断条件写反检查i % 2的判断交换 if 和 else 分支每行少一个数字range 终止值不对检查range的第二个参数从右到左时终止值设为 -1数字重复或跳跃计数器更新时机错误检查num 1的位置确保填入后再更新所有行指向同一列表二维数组初始化用了乘法检查初始化语句改用列表推导式输出结果整体偏移行索引或列索引从 1 开始检查循环的起始值统一使用 0 基索引5.4 我踩过的三个坑第一个坑是二维数组的浅拷贝。刚开始写的时候我用[[0] * n] * n创建矩阵结果发现修改一行会影响所有行。调试了十几分钟才意识到问题所在。后来查资料才知道[[]] * n创建的是 n 个指向同一个列表的引用而不是 n 个独立的列表。这个坑在 Python 里非常经典新手几乎必踩。第二个坑是range 的终止值。Python 的range(start, stop, step)是左闭右开的stop本身不包含在内。所以从右到左填充时如果写成range(n-1, 0, -1)列索引 0 就不会被遍历到导致每行少填一个数字。正确的写法是range(n-1, -1, -1)。这个细节看起来小但影响很大。第三个坑是方向判断的基准。我一开始用 1 基索引来写判断条件写成if i % 2 1表示从左到右。后来改成 0 基索引忘记同步修改判断条件导致方向全部反了。这个坑的教训是索引基准一旦确定整个代码都要保持一致不能混用。提示如果你在面试中遇到这个题建议先用 0 基索引写一版测试通过后再考虑是否需要优化。不要一上来就追求最简写法先把逻辑跑通更重要。6. 从基础蛇形到变体这个思路还能怎么扩展6.1 斜向蛇形矩阵基础蛇形矩阵是逐行填充方向在水平方向上交替。斜向蛇形矩阵则是沿着对角线方向填充方向在右上和左下之间交替。这种变体的难度更高因为需要处理对角线上的边界但核心思路是一样的确定每一步的方向然后按顺序填充。斜向蛇形的典型题目是“对角线遍历”给定一个矩阵按对角线方向交替输出所有元素。这个题在 LeetCode 上有对应的题目思路和蛇形矩阵有相通之处但边界处理更复杂。6.2 从外圈到内圈的蛇形另一种变体是从矩阵的外圈开始螺旋式地向内填充但每一圈的方向交替。这种变体结合了螺旋矩阵和蛇形矩阵的特点难度更高但如果你已经掌握了基础蛇形和螺旋矩阵这个变体也不会太难。6.3 蛇形矩阵在图像处理中的应用蛇形矩阵的填充顺序在图像处理中有实际应用。比如某些图像压缩算法会按蛇形顺序扫描像素因为这种顺序比逐行扫描能更好地利用相邻像素的相关性。另外在一些数据可视化场景中蛇形排列可以让热力图的过渡更自然。这些应用场景说明蛇形矩阵不仅仅是一道算法题它的填充顺序在某些领域是有实际意义的。理解这一点能帮你在学习的时候多一层思考这个顺序为什么这样设计它解决了什么问题。6.4 扩展练习建议如果你想进一步巩固这个知识点我建议按以下顺序练习基础蛇形矩阵本文讲的这个用两种不同的循环写法各实现一遍。对角线遍历矩阵理解斜向蛇形的边界处理。螺旋矩阵理解从外圈到内圈的填充逻辑。尝试把蛇形矩阵的填充顺序应用到一个实际的小项目里比如生成一个蛇形排列的图片缩略图网格。这个练习路径是我自己走过的从基础到变体每一步都能学到新的东西。蛇形矩阵本身不难但它是一个很好的起点能帮你建立起对矩阵类问题的整体认知。最后分享一个我在实际编码中的小习惯每次写完矩阵类题目我都会手动推演 n3 和 n4 两种情况把每一步的索引和值都写出来和代码输出对比。这个习惯帮我抓到了很多边界 bug尤其是在处理方向切换的时候。如果你也经常在矩阵题上卡壳不妨试试这个方法。
阅读完成 · 觉得有帮助?
咨询建站