题目描述
思路解析动画文字版
看一眼这个矩阵:这是我们的例子:一个 3×3 的矩阵。我们要把这 9 个数字,按对角线的规律排成一条线读出来。
先看清「对角线」长什么样:关键观察:左下到右上的这种斜线,上面每个格子的「行号 + 列号」是同一个数。我们把这个和叫做 d,d 从 0 一直数到最大,就是一条条对角线。
对角线 d=0:第 0 条对角线 d=0:只有 (0,0) 一个格子,里面是 1(紫色高亮)。这是最左上角那条最短的斜线。
对角线 d=1:第 1 条对角线 d=1:(0,1)、(1,0) 两格,值是 2 和 4。它们的行号+列号都等于 1,所以是同一条斜线。
对角线 d=2:第 2 条对角线 d=2:(0,2)、(1,1)、(2,0) 三格,值是 3、5、7。这是最中间、最长的一条斜线。
对角线 d=3 与 d=4:最后两条:d=3 是 (1,2)、(2,1),值 6、8(紫色);d=4 是 (2,2),值 9(蓝色)。整张图被切成了 5 条对角线。
五条对角线全亮一遍:把 5 条对角线一起标出来:d=0 到 d=4,一共 m+n-1 = 5 条。我们要按 d 从小到大、一条一条读完。接下来定方向。
再看方向:一上一下地翻:光按对角线还不够,方向要交替:第 0、2、4 条(偶数)从左下往右上读;第 1、3 条(奇数)从右上往左下读。一条上、一条下,像蛇形扫描。
怎么写最干净:更好写的办法:直接模拟一支笔。它沿着斜线走,走到矩阵的边界(上下左右某条边)就拐弯换方向。我们一格一格地走给你看。
转向规则(背下来就稳):笔有「向上」「向下」两种状态。向上时优先斜着走 (行-1,列+1),但若已贴着上边或右边就得拐;向下同理。先撞右/下边就右移一格,否则撞上/左边就下移一格——记住这四句就不会乱。
起步 · 笔放在 (0,0),方向:向上:笔从左上角 (0,0) 出发,当前方向「向上」。读下第一个数 1,放进输出序列。注意右边面板,结果会一格一格长出来。
撞上边 → 右移一格,转向下:在 (0,0) 想向上走,可它已经贴着最上边了,于是右移一列到 (0,1),并把方向翻成「向下」。读出 2。(0,0) 变蓝表示读过了。
向下走 · 斜着走一格:现在方向「向下」,没撞边,就斜着走一格:行 +1、列 -1,从 (0,1) 到 (1,0)。读出 4。这就是第 1 条对角线(2、4)从上往下读完了。
撞左边 → 下移一格,转向上:在 (1,0) 还想向下,可它贴着最左边了,于是下移一行到 (2,0),方向翻成「向上」。读出 7。第 2 条对角线开始了。
向上走 · 斜着走一格:方向「向上」,没撞边,斜着走:行 -1、列 +1,从 (2,0) 到 (1,1)。读出中心的 5。沿着这条最长的斜线往右上爬。
向上走 · 再走一格:继续向上斜走一格,从 (1,1) 到 (0,2),读出 3。第 2 条对角线(7、5、3)从下往上读完了。注意输出里它正是这个顺序。
撞右边(也是上边)→ 下移一格,转向下:在 (0,2) 想向上,可它撞到了右边(向上时右边优先),于是下移一行到 (1,2),方向翻成「向下」。读出 6。
向下走 · 斜着走一格:方向「向下」,没撞边,斜着走:行 +1、列 -1,从 (1,2) 到 (2,1)。读出 8。第 3 条对角线(6、8)从上往下读完。
撞下边 → 右移一格,转向上:在 (2,1) 想向下,撞到了下边,于是右移一列到 (2,2),方向翻成「向上」。读出最后的 9。9 个数全读完了。
走完 · 全部读出:这支笔走过的轨迹就是答案:[1,2,4,7,5,3,6,8,9]。整张图只走了一遍,每个格子读一次,干净利落。
回看轨迹 · 偶数条向上读:回看一下:紫色高亮的是偶数条对角线(d=0、2、4),它们都是从下往上读的——1;7、5、3;9。
回看轨迹 · 奇数条向下读:紫色高亮的是奇数条对角线(d=1、3),它们都是从上往下读的——2、4;6、8。和偶数条正好方向相反,这就是「上下交替」。
回看轨迹 · 笔走过的完整 Z 字:把这支笔走过的路连起来看:从紫色起点 (0,0) 出发,沿斜线上上下下地翻,最后到红色终点 (2,2)。这条 Z 字轨迹就是对角线遍历的全部秘密。
落到代码 · 一支笔的循环:代码就是把刚才那支笔写成循环:一共走 m×n 步,每步先把当前格子塞进结果,再根据「向上/向下 + 有没有撞边」算出下一步落点。下面看三语言实现。
本质金句:螺旋矩阵、对角线遍历这类题的共同套路就是「模拟」:想清楚走的方向、什么时候转向,让一支笔老老实实把每个格子走一遍,代码就出来了。
易错实演 · 右上角判定顺序:在右上角 (0,2)(红色),向上走时它既贴右边又贴上边。必须先判右边(下移到 (1,2));若先判上边会想右移、列就越界了。这是本题最刁的一格。
边界三连:单行、单列、单格这些极端形状,模拟法都能自然兜住——同一套转向规则照跑,不用额外特判,这正是模拟法的好处。
面试追问:面试常追问「还有没有别的解法」和「角落为什么这么判」。把模拟法和分组法都讲清楚,再点透角落的判定顺序,思路就很完整了。
参考代码
class Solution: def findDiagonalOrder(self, mat): if not mat or not mat[0]: return [] m, n = len(mat), len(mat[0]) res = [] r = c = 0 up = True for _ in range(m * n): res.append(mat[r][c]) if up: if c == n - 1: r += 1; up = False elif r == 0: c += 1; up = False else: r -= 1; c += 1 else: if r == m - 1: c += 1; up = True elif c == 0: r += 1; up = True else: r += 1; c -= 1 return res复杂度
- 时间:O(m·n),每个格子恰好被访问、读出一次
- 空间:O(1),除返回数组外只用 r、c、up 几个变量,原地模拟
易错点
面试追问把动画讲成自己的话
追问除了模拟一支笔,还有别的写法吗?
追问如果方向反过来,第一条从上往下读呢?
追问为什么向上走要先判右边、向下走要先判下边?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。再去主线里挑下一道练手。
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题