重塑矩阵 图解题解
这道题到底在问什么
- 输入
- mat=[[1,2],[3,4]], r=1, c=4
- 输出
- [[1,2,3,4]] 元素总数都是 4,能重塑
- 输入
- mat=[[1,2],[3,4]], r=2, c=4
- 输出
- [[1,2],[3,4]] 4 ≠ 8,塞不下,原样返回
先想最直接的笨办法
搭一个 4 行 2 列的空架子,圆点表示还没填。等下把刚才那条序列,一个一个按行序填进来。(动画第 15 步)
最优解:为什么这么做
一句话答案: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。
▶ 动画逐步走查(共 24 步)——想跟着动画一帧帧对照就展开
- 3记住这把尺子:同一条直线序列,用 n 算旧坐标、用 c 算新坐标。下面每一帧都在套它。
- 4m=2 行,n=4 列,要重塑成 r=4 行、c=2 列这是原矩阵,2 行 4 列,一共 8 个数。目标是把它们重新装成 4 行 2 列。
- 5m×n = 2×4 = 8,r×c = 4×2 = 8,相等,可以重塑动手前先算总数。原矩阵 2×4 是 8 个,目标 4×2 也是 8 个,正好装得下,可以重塑。要是总数对不上,后面会演,直接把原矩阵还回去。
- 6编号 k=0,旧坐标 = (0 整除 4, 0 取余 4) = (0,0),值 1展平就是从左上角出发,一行读完再读下一行。先读 (0,0) 这一格,值是 1,把它放到序列第一位。
- 7编号 k=1,旧坐标 = (1 整除 4, 1 取余 4) = (0,1),值 2继续沿着这一行往右走,读 (0,1) 的 2,接进序列,现在序列里有 2 个数了。
- 8编号 k=2,旧坐标 = (2 整除 4, 2 取余 4) = (0,2),值 3继续沿着这一行往右走,读 (0,2) 的 3,接进序列,现在序列里有 3 个数了。
- 9编号 k=3,旧坐标 = (3 整除 4, 3 取余 4) = (0,3),值 4继续沿着这一行往右走,读 (0,3) 的 4,接进序列,现在序列里有 4 个数了。
- 10编号 k=4,旧坐标 = (4 整除 4, 4 取余 4) = (1,0),值 5这一行读完了,换到下一行的开头 (1,0),值 5 接到序列后面。注意是整行整行地往下走,顺序不能乱。
- 11编号 k=5,旧坐标 = (5 整除 4, 5 取余 4) = (1,1),值 6继续沿着这一行往右走,读 (1,1) 的 6,接进序列,现在序列里有 6 个数了。
- 12编号 k=6,旧坐标 = (6 整除 4, 6 取余 4) = (1,2),值 7继续沿着这一行往右走,读 (1,2) 的 7,接进序列,现在序列里有 7 个数了。
- 13编号 k=7,旧坐标 = (7 整除 4, 7 取余 4) = (1,3),值 8继续沿着这一行往右走,读 (1,3) 的 8,接进序列,现在序列里有 8 个数了。
- 14行优先序列 = 1, 2, 3, 4, 5, 6, 7, 8整张矩阵读完了,8 个数按行序排成了一条直线。接下来把这条线按 4×2 的新形状重新装回去。
- 15先开一个 4 行 2 列的空矩阵,等着按序列填搭一个 4 行 2 列的空架子,圆点表示还没填。等下把刚才那条序列,一个一个按行序填进来。
- 16编号 k=0,新坐标 = (0 整除 2, 0 取余 2) = (0,0),值 1从序列头部拿出第一个数 1,按新宽度 c=2 算位置:0 整除 2 是第 0 行,0 取余 2 是第 0 列,填到 (0,0)。
- 17编号 k=1,新坐标 = (1 整除 2, 1 取余 2) = (0,1),值 2接着填第 1 个数 2,落在 (0,1)。可以看到序列是怎么被切成一段段、铺进新形状的。
- 18编号 k=2,新坐标 = (2 整除 2, 2 取余 2) = (1,0),值 3新矩阵这一行也填满了,换行。第 2 个数 3 落到 (1,0),正好是下一行的开头。
- 19编号 k=3,新坐标 = (3 整除 2, 3 取余 2) = (1,1),值 4接着填第 3 个数 4,落在 (1,1)。可以看到序列是怎么被切成一段段、铺进新形状的。
- 20编号 k=4,新坐标 = (4 整除 2, 4 取余 2) = (2,0),值 5新矩阵这一行也填满了,换行。第 4 个数 5 落到 (2,0),正好是下一行的开头。
- 21编号 k=5,新坐标 = (5 整除 2, 5 取余 2) = (2,1),值 6接着填第 5 个数 6,落在 (2,1)。可以看到序列是怎么被切成一段段、铺进新形状的。
- 22编号 k=6,新坐标 = (6 整除 2, 6 取余 2) = (3,0),值 7新矩阵这一行也填满了,换行。第 6 个数 7 落到 (3,0),正好是下一行的开头。
- 23编号 k=7,新坐标 = (7 整除 2, 7 取余 2) = (3,1),值 8接着填第 7 个数 8,落在 (3,1)。可以看到序列是怎么被切成一段段、铺进新形状的。
- 248 个数全部按行序就位,返回这个新矩阵8 个数全填完了。新矩阵每一行是 [1,2]、[3,4]、[5,6]、[7,8],数字顺序和原来一模一样,只是行列变了。这就是重塑的结果。
- 25m×n = 8,但 r×c = 3×3 = 9,8 ≠ 9再看一个塞不下的情况。还是这张 8 个数的矩阵,如果要重塑成 3×3,那是 9 个格子,数字不够填、总数对不上。
- 26总数不等,无法重塑,返回原矩阵 mat这种时候不强行重塑,按题目要求把原矩阵原封不动地还回去。所以总数校验一定要放在最前面。
⚠️ 容易写错的地方
✗ 错:忘了先校验总数就开搬
✓ 对:先判断 m×n 是否等于 r×c,不等就返回原矩阵
总数对不上时硬填会越界或漏数,题目要求这种情况原样返回
✗ 错:旧坐标也用 c 去算列
✓ 对:旧坐标用原宽度 n,新坐标才用目标宽度 c
读原矩阵要按它自己的列数 n 定位,用错宽度会读到完全错的格子
✗ 错:整除和取余记反,行列颠倒
✓ 对:整除算行(走了几整行),取余算列(行内偏移)
记反会把矩阵转置或错位,数字顺序全乱
完整代码(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 *
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 ansC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#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;
class Solution {
public:
vector<vector<int>> matrixReshape(vector<vector<int>>& mat, int r, int c) {
int m = mat.size(), n = mat[0].size();
if (m * n != r * c) {
return mat;
}
vector<vector<int>> ans(r, vector<int>(c));
for (int i = 0; i < m * n; ++i) {
ans[i / c][i % c] = mat[i / n][i % n];
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int[][] matrixReshape(int[][] mat, int r, int c) {
int m = mat.length, n = mat[0].length;
if (m * n != r * c) {
return mat;
}
int[][] ans = new int[r][c];
for (int i = 0; i < m * n; ++i) {
ans[i / c][i % c] = mat[i / n][i % n];
}
return ans;
}
}复杂度
时间
O(m·n)
每个元素恰好搬一次,总共 m 乘 n 个,无重复无回头
空间
O(m·n)
新矩阵要存下全部元素;不计返回结果本身则是 O(1) 额外变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 重塑矩阵 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
不真开一个一维数组,能直接在两套行列坐标间转换吗?+
能,参考代码就是这么做的。拉成一条直线只是帮你想清顺序,真正实现时根本不用建那条中间序列。用一个编号从 0 跑到总数减 1,读的时候拿编号整除 n、取余 n 定位原矩阵,写的时候拿编号整除 c、取余 c 定位新矩阵,一层循环就搬完,还省下了那条直线占的空间。
要是原矩阵每行长度不一样,这套整除取余还成立吗?+
不成立。整除取余能定位,前提是每行都是齐整的 n 列,编号除以 n 才对得上行、余数才对得上列。本题保证是规整的 m 行 n 列矩阵,可以放心用。如果换成每行长短不一的锯齿数组,这套坐标公式就塌了,得老老实实按行把元素一个个展开成序列、再按目标宽度切回去,思路还是行序展平加回填,只是定位方式换掉。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 重塑矩阵 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。