螺旋矩阵 IV 图解题解
这道题到底在问什么
- 输入
- m=3, n=4, head=[3,7,1,9,4,8,2,6]
- 输出
- [[3,7,1,9],[-1,-1,-1,4],[-1,6,2,8]]
- 输入
- m=1, n=4, head=[0,1,2]
- 输出
- [[0,1,2,-1]]
最优解:一步一步想明白
- 3记牢这一句:走不走得动,只看下一格是不是「界内且还是 -1」。是就走,不是就顺时针转向。这一条判断,既管住了拐弯,也顺手把剩余格子留成了 -1。下面从左上角开始一步步走。
- 4整张矩阵先铺成 -1开工前先把整张 3 乘 4 的矩阵在心里铺成 -1,舞台上没写过的格子先用一个点占位。指针停在左上角 (0,0),初始方向是向右。侧栏那一列 3、7、1、9、4、8、2、6,就是链表从头到尾要写进来的 8 个值。准备好了,开始沿螺旋往里填。
- 5第 1 个值落格第一步最简单:直接在起点 (0,0) 写下链表头的值 3,这一格变成紫色表示刚写。链表指针往后挪一位,侧栏顶上的 3 划走了。接着要决定下一格往哪儿走。
- 6下一格畅通,继续直行站在刚写完的这一格,判断下一步。朝向右探一格,下一格 (0,1) 在界内、而且还是 -1,没填过,直接迈过去。
- 7第 2 个值落格把链表当前值 7 写进 (0,1),这一格点亮成紫色,之前写过的格子退成蓝色。链表再往后一位,侧栏又少一个值。现在已经写满 2 个格子。
- 8下一格畅通,继续直行站在刚写完的这一格,判断下一步。朝向右探一格,下一格 (0,2) 在界内、而且还是 -1,没填过,直接迈过去。
- 9第 3 个值落格把链表当前值 1 写进 (0,2),这一格点亮成紫色,之前写过的格子退成蓝色。链表再往后一位,侧栏又少一个值。现在已经写满 3 个格子。
- 10下一格畅通,继续直行站在刚写完的这一格,判断下一步。朝向右探一格,下一格 (0,3) 在界内、而且还是 -1,没填过,直接迈过去。
- 11第 4 个值落格把链表当前值 9 写进 (0,3),这一格点亮成紫色,之前写过的格子退成蓝色。链表再往后一位,侧栏又少一个值。现在已经写满 4 个格子。
- 12下一格越界或已填,顺时针转向站在刚写完的这一格,判断下一步。朝向右探一格会走出矩阵,于是顺时针转到向下,探到 (1,3) 在界内又还是 -1,迈过去。 注意转向不是数着走了几步才拐,而是探到走不动的那一刻才拐,这样非方形矩阵也不会算错。
- 13第 5 个值落格把链表当前值 4 写进 (1,3),这一格点亮成紫色,之前写过的格子退成蓝色。链表再往后一位,侧栏又少一个值。现在已经写满 5 个格子。
- 14下一格畅通,继续直行站在刚写完的这一格,判断下一步。朝向下探一格,下一格 (2,3) 在界内、而且还是 -1,没填过,直接迈过去。
- 15第 6 个值落格把链表当前值 8 写进 (2,3),这一格点亮成紫色,之前写过的格子退成蓝色。链表再往后一位,侧栏又少一个值。现在已经写满 6 个格子。
- 16下一格越界或已填,顺时针转向站在刚写完的这一格,判断下一步。朝向下探一格会走出矩阵,于是顺时针转到向左,探到 (2,2) 在界内又还是 -1,迈过去。 注意转向不是数着走了几步才拐,而是探到走不动的那一刻才拐,这样非方形矩阵也不会算错。
- 17第 7 个值落格把链表当前值 2 写进 (2,2),这一格点亮成紫色,之前写过的格子退成蓝色。链表再往后一位,侧栏又少一个值。现在已经写满 7 个格子。
- 18下一格畅通,继续直行站在刚写完的这一格,判断下一步。朝向左探一格,下一格 (2,1) 在界内、而且还是 -1,没填过,直接迈过去。
- 19第 8 个值落格把链表当前值 6 写进 (2,1),这一格点亮成紫色,之前写过的格子退成蓝色。链表再往后一位,侧栏又少一个值。现在已经写满 8 个格子。
- 208 个值全部落格链表的 8 个值全部写完,侧栏空了。螺旋才走到 (2,1) 就停在了半路,根本没绕回中间。矩阵一共 12 格,只写了 8 格,还剩下 4 格从头到尾没人碰过。这 4 个空格该怎么办,下一步见分晓。
- 21未被螺旋覆盖的格子把还显示点的空格挑出来看:(1,0)、(1,1)、(1,2)、(2,0),这 4 个格子链表没走到。它们正好卡在螺旋还没绕进去的中间地带。因为一开始整张矩阵就是 -1,这些格子其实一直安安静静地保持着 -1,我们什么都不用额外做。
- 224 个空格写上 -1现在把那 4 个空格的真面目亮出来:它们都是 -1,刷成浅蓝。这一步其实没做任何新动作,只是把「一开始就铺好的 -1」显示出来而已。这正是先整张铺 -1 的妙处:剩余格子天然就是 -1,省得最后再回头补。到此,12 个格子全部有值。
- 23答案 [[3,7,1,9],[-1,-1,-1,4],[-1,6,2,8]]回放一遍全程。绿色是链表螺旋填出来的 8 个值:先沿第一行 3、7、1、9 向右,撞墙转下走 4、8,再转左走 2、6,链表就空了。浅蓝的 4 个 -1 是没被螺旋绕到的空位。合起来就是最终答案:第一行 3、7、1、9,第二行 -1、-1、-1、4,第三行 -1、6、2、8。
⚠️ 容易写错的地方
✗ 错:按「走 n 步再拐、走 m-1 步再拐」的层数法数着走
✓ 对:统一判断「下一格越界或已填就转向」
数步数法在链表提前走完、或者行列不等时边界极易写错;按格判断天然处理提前结束和非方形矩阵
✗ 错:Java 里忘了把矩阵初始化成 -1
✓ 对:用 Arrays 的 fill 把每行刷成 -1
Java 的 int 数组默认值是 0,不填就会把没写到的格子留成 0 而不是 -1,直接答案错
✗ 错:转一次向就以为新方向一定能走
✓ 对:转向后要重新判断,必要时连续转
走到角落时可能一次转向后新方向也越界或已填,得靠 k 加一取模的循环一直转到找着合法格为止
✗ 错:另开一张 visited 表记录哪格填过
✓ 对:直接看那格是不是还等于 -1
题目节点值都在 0 到 1000、非负,-1 不可能是真实值,用它当未填标记既省一张表又不会误判
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
from string import *
from operator import *
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def spiralMatrix(self, m: int, n: int, head: Optional[ListNode]) -> List[List[int]]:
ans = [[-1] * n for _ in range(m)]
i = j = k = 0
dirs = (0, 1, 0, -1, 0)
while 1:
ans[i][j] = head.val
head = head.next
if head is None:
break
while 1:
x, y = i + dirs[k], j + dirs[k + 1]
if 0 <= x < m and 0 <= y < n and ans[x][y] == -1:
i, j = x, y
break
k = (k + 1) % 4
return ansC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <optional>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
vector<vector<int>> spiralMatrix(int m, int n, ListNode* head) {
vector<vector<int>> ans(m, vector<int>(n, -1));
int i = 0, j = 0, k = 0;
const int dirs[5] = {0, 1, 0, -1, 0};
while (1) {
ans[i][j] = head->val;
head = head->next;
if (!head) {
break;
}
while (1) {
int x = i + dirs[k], y = j + dirs[k + 1];
if (x >= 0 && x < m && y >= 0 && y < n && ans[x][y] == -1) {
i = x;
j = y;
break;
}
k = (k + 1) % 4;
}
}
return ans;
}
};Java
import java.util.*;
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(){} TreeNode(int val){this.val=val;} TreeNode(int val, TreeNode left, TreeNode right){this.val=val;this.left=left;this.right=right;} }
class ListNode { int val; ListNode next; ListNode(){} ListNode(int val){this.val=val;} ListNode(int val, ListNode next){this.val=val;this.next=next;} }
class Solution {
public int[][] spiralMatrix(int m, int n, ListNode head) {
int[][] ans = new int[m][n];
for (int[] row : ans) {
Arrays.fill(row, -1);
}
int i = 0, j = 0, k = 0;
final int[] dirs = {0, 1, 0, -1, 0};
while (true) {
ans[i][j] = head.val;
head = head.next;
if (head == null) {
break;
}
while (true) {
int x = i + dirs[k], y = j + dirs[k + 1];
if (x >= 0 && x < m && y >= 0 && y < n && ans[x][y] == -1) {
i = x;
j = y;
break;
}
k = (k + 1) % 4;
}
}
return ans;
}
}复杂度
时间
O(m·n)
m 行 n 列共 m 乘 n 个格子。初始化整张矩阵是 m 乘 n;主循环每写一个链表值前进一格,内层找下一格时最多连转几次方向,但每格总的转向次数是常数摊销,整体随格子总数线性
空间
O(1)
按峰值算,只用了 i、j、k 几个下标变量,没有另开访问表。必须返回的结果矩阵是 O(m 乘 n),但那是题目要求的输出、不计入额外空间;真正的辅助空间是常数
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 螺旋矩阵 IV 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题的核心思路一句话怎么说?+
把整张矩阵先铺成 -1,一个指针从左上角沿右下左上顺时针螺旋走,每格写下链表当前值。走下一步前先探当前方向的下一格,若越界或已填过就顺时针转向,直到探到界内且仍为 -1 的格子。链表走完就停,没写到的格子自然留着 -1。
怎么判断该转向,又怎么保证转向后还合法?+
判断依据是下一格是否「在界内且还等于 -1」。用一个方向下标 k 配上方向数组取偏移;探到走不动就 k 加一对 4 取模换下个方向,并且是放在一个内层循环里一直转,直到找着能落脚的格子,所以哪怕在角落连续越界也能连转好几次转到位。
复杂度是多少,空间能不能做到常数?+
时间 O(m 乘 n),每个格子被写入一次,转向的总次数是常数摊销。额外空间 O(1),只用几个下标变量,靠「格子是否还等于 -1」当未填标记,不用另开访问表;必须返回的结果矩阵是题目要求的输出,不计入额外空间。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 螺旋矩阵 IV 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。