题目描述
思路解析
一句话答案:LeetCode 566 重塑矩阵:把所有数按行序拉成一条直线,再用整除定行、取余定列换算新旧坐标,搬进 r×c 的新矩阵;总数对不上就原样返回,时间 O(m·n)、空间 O(m·n)。
把矩阵换个行列形状,要返回什么
给一个 m 行 n 列的矩阵 mat,再给目标行数 r、列数 c。要把 mat 里的数按原来的行遍历顺序,也就是一行读到底再换下一行,装进 r 行 c 列的新矩阵。题面给的 mat=[[1,2],[3,4]]、r=1、c=4,4 个数铺成一行,返回 [[1,2,3,4]];换成 r=2、c=4,新矩阵要 8 个格子、原矩阵只有 4 个数,塞不下就原样还回 mat。
同一组行列坐标,两张矩阵指的不是同一个数
同样写着「第 1 行第 0 列」,在原矩阵和新矩阵里往往不是同一个数——原矩阵一行 n 个、新矩阵一行 c 个,行宽一变,同一组坐标就指向不同格子。照着新矩阵的格子去原矩阵对号入座会取错,得先有个跟形状无关的东西把两套坐标挂上钩。
先拉成一条直线,再按新宽度折回去
先不管形状,把原矩阵按行序拉成一条直线,(0,0)、(0,1)…一行接一行排成一串从 0 起编号的序列,这条线是两张矩阵共用的尺子。原矩阵一行 n 个数,直线上第 i 个数回原矩阵就在第 i 整除 n 行、第 i 取余 n 列;新矩阵一行 c 个数,同一个数落进新矩阵就在第 i 整除 c 行、第 i 取余 c 列。整除算走满了几整行,取余算这一行里的偏移,折行宽度不过是从 n 换成 c。
先对总数,再用一个编号把数搬完
步骤就两件事。头一件是校验总数:只有 m 乘 n 等于 r 乘 c、两张矩阵格子一样多才装得下,一旦不等就直接返回原矩阵 mat。第二件是搬运:开一个 r 行 c 列、先全填 0 的新矩阵 ans,让编号 i 从 0 跑到 m 乘 n 减 1,每步把原矩阵第 i 整除 n 行、第 i 取余 n 列的数写到 ans 第 i 整除 c 行、第 i 取余 c 列。读用原宽度 n、写用新宽度 c,一趟循环所有数各就各位。
拿 [[1,2],[3,4]] 重塑成 1 行 4 列过一趟
就用题面这张 mat=[[1,2],[3,4]],目标 r=1、c=4。先对总数:原矩阵 2 乘 2、目标 1 乘 4 都是 4 个,相等。读用原宽度 n=2、写用新宽度 c=4,编号从 0 数到 3。编号 0:第 0 整除 2=0 行、0 取余 2=0 列取到 1,进新矩阵 (0,0);编号 1:原矩阵 (0,1) 取到 2,进 (0,1);编号 2:第 2 整除 2=1 行、2 取余 2=0 列取到 3,进 (0,2);编号 3:原矩阵 (1,1) 取到 4,进 (0,3)。四步填完,新矩阵是 [[1,2,3,4]],顺序跟原来一字不差。
为什么是 O(m·n),两种极端形状会怎样
时间上每个数只读写一遍、不重不回头,共 m 乘 n 个,所以 O(m·n);新矩阵要存下全部 m 乘 n 个数,空间同样 O(m·n)。两处写法一错结果全乱:省掉总数校验直接开搬,碰上 r 乘 c 跟原矩阵对不上时轻则漏数、重则越界,题目要的正是原样退回 mat;读原矩阵若也拿新宽度 c 定列会跳到别的格子,得用它自家宽度 n,写新矩阵才用 c。边界反而简单:r=1 摊成一整行、c=1 摊成一整列都合法,红线是 m 乘 n 得等于 r 乘 c。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这把尺子:同一条直线序列,用 n 算旧坐标、用 c 算新坐标。下面每一帧都在套它。
原矩阵 · 2×4 · 共 8 个元素:这是原矩阵,2 行 4 列,一共 8 个数。目标是把它们重新装成 4 行 2 列。
第一步 · 先对总数:动手前先算总数。原矩阵 2×4 是 8 个,目标 4×2 也是 8 个,正好装得下,可以重塑。要是总数对不上,后面会演,直接把原矩阵还回去。
展平第 0 个 · 读 (0,0):展平就是从左上角出发,一行读完再读下一行。先读 (0,0) 这一格,值是 1,把它放到序列第一位。
展平第 1 个 · 读 (0,1):继续沿着这一行往右走,读 (0,1) 的 2,接进序列,现在序列里有 2 个数了。
展平第 2 个 · 读 (0,2):继续沿着这一行往右走,读 (0,2) 的 3,接进序列,现在序列里有 3 个数了。
展平第 3 个 · 读 (0,3):继续沿着这一行往右走,读 (0,3) 的 4,接进序列,现在序列里有 4 个数了。
展平第 4 个 · 读 (1,0):这一行读完了,换到下一行的开头 (1,0),值 5 接到序列后面。注意是整行整行地往下走,顺序不能乱。
展平第 5 个 · 读 (1,1):继续沿着这一行往右走,读 (1,1) 的 6,接进序列,现在序列里有 6 个数了。
展平第 6 个 · 读 (1,2):继续沿着这一行往右走,读 (1,2) 的 7,接进序列,现在序列里有 7 个数了。
展平第 7 个 · 读 (1,3):继续沿着这一行往右走,读 (1,3) 的 8,接进序列,现在序列里有 8 个数了。
展平完成 · 8 个数排成一条线:整张矩阵读完了,8 个数按行序排成了一条直线。接下来把这条线按 4×2 的新形状重新装回去。
准备目标矩阵 · 4×2 空架子:搭一个 4 行 2 列的空架子,圆点表示还没填。等下把刚才那条序列,一个一个按行序填进来。
回填第 0 个 · 放进 (0,0):从序列头部拿出第一个数 1,按新宽度 c=2 算位置:0 整除 2 是第 0 行,0 取余 2 是第 0 列,填到 (0,0)。
回填第 1 个 · 放进 (0,1):接着填第 1 个数 2,落在 (0,1)。可以看到序列是怎么被切成一段段、铺进新形状的。
回填第 2 个 · 放进 (1,0):新矩阵这一行也填满了,换行。第 2 个数 3 落到 (1,0),正好是下一行的开头。
回填第 3 个 · 放进 (1,1):接着填第 3 个数 4,落在 (1,1)。可以看到序列是怎么被切成一段段、铺进新形状的。
回填第 4 个 · 放进 (2,0):新矩阵这一行也填满了,换行。第 4 个数 5 落到 (2,0),正好是下一行的开头。
回填第 5 个 · 放进 (2,1):接着填第 5 个数 6,落在 (2,1)。可以看到序列是怎么被切成一段段、铺进新形状的。
回填第 6 个 · 放进 (3,0):新矩阵这一行也填满了,换行。第 6 个数 7 落到 (3,0),正好是下一行的开头。
回填第 7 个 · 放进 (3,1):接着填第 7 个数 8,落在 (3,1)。可以看到序列是怎么被切成一段段、铺进新形状的。
重塑完成 · 4×2 新矩阵:8 个数全填完了。新矩阵每一行是 [1,2]、[3,4]、[5,6]、[7,8],数字顺序和原来一模一样,只是行列变了。这就是重塑的结果。
反例 · 想重塑成 3×3:再看一个塞不下的情况。还是这张 8 个数的矩阵,如果要重塑成 3×3,那是 9 个格子,数字不够填、总数对不上。
反例 · 直接原样返回:这种时候不强行重塑,按题目要求把原矩阵原封不动地还回去。所以总数校验一定要放在最前面。
边界先想清:能拉成一行、也能拉成一列,只要总数等;总数不等就原样还回。
两个高频追问:用编号直接转坐标可省掉中间数组;规整矩阵才能用整除取余定位。
参考代码
from __future__ import annotationsfrom typing import *from collections import *from functools import *from itertools import *from math import *from heapq import *from bisect import *class Solution: def matrixReshape(self, mat: List[List[int]], r: int, c: int) -> List[List[int]]: m, n = len(mat), len(mat[0]) if m * n != r * c: return mat ans = [[0] * c for _ in range(r)] for i in range(m * n): ans[i // c][i % c] = mat[i // n][i % n] return ans复杂度
- 时间:O(m·n),每个元素恰好搬一次,总共 m 乘 n 个,无重复无回头
- 空间:O(m·n),新矩阵要存下全部元素;不计返回结果本身则是 O(1) 额外变量
易错点
面试追问把动画讲成自己的话
追问不展平成一条线,能直接用两组行列坐标互相转换吗?
追问如果原矩阵每行长度不一定相等怎么办?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
图像渲染
LeetCode 733 · 简单 · 沿着 图论套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题