题目描述
思路解析
一句话答案:LeetCode 867 转置矩阵:把行列对调,原来第 i 行第 j 列的数搬到新矩阵第 j 行第 i 列,也就是 ans[i][j]=matrix[j][i];非方阵得另开 n 行 m 列的新矩阵逐格搬,时间 O(m·n)。
转置矩阵,到底要返回一个什么形状
给一个二维整数矩阵 matrix,把它转置后返回。转置就是行列对调:原来第 i 行第 j 列的数,跑到新矩阵第 j 行第 i 列。形状也跟着变,原矩阵 m 行 n 列,转置后就是 n 行 m 列。题面 matrix=[[1,2,3],[4,5,6],[7,8,9]] 这种方阵转置后是 [[1,4,7],[2,5,8],[3,6,9]];而 2 行 3 列的矩阵转置后就成了 3 行 2 列,长宽掉个个儿。
想在原矩阵上就地对调,会卡在哪
一个直接的念头是不另开空间,就在原矩阵上把两个位置的数对调。方阵勉强行得通——沿主对角线,把第 i 行第 j 列和第 j 行第 i 列两两交换即可。可本题不保证是方阵:题面那个 2 行 3 列的矩阵转置后是 3 行 2 列,行数列数都变了,原来的格子装不下新形状,就地交换无处落脚。除非确定是方阵,否则老实另开新矩阵才稳妥。
行变列:第 i 行第 j 列的数该落到哪
转置的核心就一条对应关系:原矩阵第 i 行第 j 列的数,在新矩阵里落到第 j 行第 i 列,两个下标对调,即 ans[i][j] = matrix[j][i]。原矩阵横着的一行,转置后变成竖着的一列;一列则变成一行。Python 里一句 list(zip(*matrix)) 就够了:zip 把每一行同一位置的元素打包成一组,每组恰好是原矩阵的一列,正是转置。
新矩阵先开 n 行 m 列,再逐格搬数
落到实现上分两步。先量原矩阵尺寸:m 是行数,n 是列数,据此开一个 n 行 m 列的空矩阵 ans。然后两层循环逐格填:外层 i 走新矩阵的行、也就是原矩阵的列,内层 j 走新矩阵的列、也就是原矩阵的行,每格执行 ans[i][j] = matrix[j][i],把对应的数各搬一次,不重不漏。
拿题面的 3×3 方阵亲手转一遍
用题面第一个例子 matrix=[[1,2,3],[4,5,6],[7,8,9]]。原矩阵第 0 行是 1、2、3,第 1 行是 4、5、6,第 2 行是 7、8、9。转置按列取:新矩阵第 0 行取原来的第 0 列,是 1、4、7;第 1 行取第 1 列,是 2、5、8;第 2 行取第 2 列,是 3、6、9,拼起来就是 [[1,4,7],[2,5,8],[3,6,9]],正好是题面要的答案。主对角线上的 1、5、9 位置没动,它们的行下标和列下标本就相等;对角线外的数才真换了座,比如 2 从第 0 行第 1 列挪到了第 1 行第 0 列。
搬错一个下标,就等于原样抄了一遍
最易出错的是下标和形状。把 ans[i][j] = matrix[j][i] 里的下标忘了对调、写成 ans[i][j] = matrix[i][j],等于把原矩阵原样抄一遍,压根没转置。形状开反也一样致命:新矩阵本该 n 行 m 列,若开成 m 行 n 列,碰上非方阵不是越界就是留空。复杂度上,矩阵里 m 乘 n 个数每个只经手一趟,时间 O(m·n);新矩阵要存下全部数,空间 O(m·n),不计返回结果则只用几个循环变量,是 O(1)。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记牢这把尺子:新坐标 (i,j) 去原矩阵的 (j,i) 取数。后面每一帧都在套它。
原矩阵 · 2 行 4 列 · 共 8 个数:这是原矩阵,2 行 4 列,一共 8 个数。目标是把它翻成 4 行 2 列。先记住它现在的样子,等会儿对比着看。
想象沿主对角线翻折:转置的几何直觉:沿着左上到右下这条主对角线翻折。对角线上的数原地不动,其它数像照镜子一样,行列坐标对调位置。
准备新矩阵 · 4 行 2 列空架子:搭一个 4 行 2 列的空架子,圆点表示还没填。注意它的形状,行数等于原来的列数,列数等于原来的行数,正好掉了个个儿。接下来一个一个把数搬进来。
读原矩阵 第 0 行第 0 列 = 1:从原矩阵 第 0 行第 0 列 起步,读到的值是 1。它要搬去新矩阵 第 0 行第 0 列,注意两个坐标对调了。
写新矩阵 第 0 行第 0 列 = 1:把 1 写进新矩阵 第 0 行第 0 列。看坐标:原来是 (0,0),现在是 (0,0),两个下标对调,这就是转置。
读原矩阵 第 1 行第 0 列 = 5:接着读原矩阵 第 1 行第 0 列 的 5,准备搬到新矩阵 第 0 行第 1 列。
写新矩阵 第 0 行第 1 列 = 5:把 5 落到新矩阵 第 0 行第 1 列,第 0 行凑齐了。这一行 1、5,正好是原矩阵第 0 列那一竖排。
读原矩阵 第 0 行第 1 列 = 2:新矩阵这一行填满了,换行。回原矩阵读 第 0 行第 1 列 的 2,它会落到新矩阵 第 1 行第 0 列,开启新一行。
写新矩阵 第 1 行第 0 列 = 2:把 2 写进新矩阵 第 1 行第 0 列。看坐标:原来是 (0,1),现在是 (1,0),两个下标对调,这就是转置。
读原矩阵 第 1 行第 1 列 = 6:接着读原矩阵 第 1 行第 1 列 的 6,准备搬到新矩阵 第 1 行第 1 列。
写新矩阵 第 1 行第 1 列 = 6:把 6 落到新矩阵 第 1 行第 1 列,第 1 行凑齐了。这一行 2、6,正好是原矩阵第 1 列那一竖排。
读原矩阵 第 0 行第 2 列 = 3:新矩阵这一行填满了,换行。回原矩阵读 第 0 行第 2 列 的 3,它会落到新矩阵 第 2 行第 0 列,开启新一行。
写新矩阵 第 2 行第 0 列 = 3:把 3 写进新矩阵 第 2 行第 0 列。看坐标:原来是 (0,2),现在是 (2,0),两个下标对调,这就是转置。
读原矩阵 第 1 行第 2 列 = 7:接着读原矩阵 第 1 行第 2 列 的 7,准备搬到新矩阵 第 2 行第 1 列。
写新矩阵 第 2 行第 1 列 = 7:把 7 落到新矩阵 第 2 行第 1 列,第 2 行凑齐了。这一行 3、7,正好是原矩阵第 2 列那一竖排。
读原矩阵 第 0 行第 3 列 = 4:新矩阵这一行填满了,换行。回原矩阵读 第 0 行第 3 列 的 4,它会落到新矩阵 第 3 行第 0 列,开启新一行。
写新矩阵 第 3 行第 0 列 = 4:把 4 写进新矩阵 第 3 行第 0 列。看坐标:原来是 (0,3),现在是 (3,0),两个下标对调,这就是转置。
读原矩阵 第 1 行第 3 列 = 8:接着读原矩阵 第 1 行第 3 列 的 8,准备搬到新矩阵 第 3 行第 1 列。
写新矩阵 第 3 行第 1 列 = 8:把 8 落到新矩阵 第 3 行第 1 列,第 3 行凑齐了。这一行 4、8,正好是原矩阵第 3 列那一竖排。
转置完成 · 4 行 2 列:8 个数全搬完了。新矩阵是 [1,5]、[2,6]、[3,7]、[4,8],4 行 2 列。和原来的 2 行 4 列一对比,长宽互换了,数字一个没少。
回放 · 原来的一行,变成了新的一列:再回看一眼最直观的对应:新矩阵竖着的第 0 列是 1、2、3、4,正好是原矩阵横着的第 0 行。行变列、列变行,这就是转置的全貌。
边界先想清:单个数原样返回;一整行能立起来变成一列,一整列也能躺平变成一行。
两个高频追问:方阵才能原地转置;zip 把列打包成行,等价于转置。
参考代码
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 transpose(self, matrix: List[List[int]]) -> List[List[int]]: return list(zip(*matrix))复杂度
- 时间:O(m·n),每个元素恰好搬一次,总共 m 乘 n 个,无重复无回头
- 空间:O(m·n),新矩阵要存下全部元素;不计返回结果本身,额外只用了循环变量,是 O(1)
易错点
面试追问把动画讲成自己的话
追问能不能原地转置,不开新矩阵?
追问Python 那个 zip 写法是什么原理?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
矩阵中战斗力最弱的 K 行
LeetCode 1337 · 简单 · 沿着 图论套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题