旋转矩阵
1. 题目
给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。
不占用额外内存空间能否做到?
2. 示例
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
3. 解题思路
3.1 方法一:使用辅助数组
矩阵旋转的特点:对于矩阵中第 i i ii ii 行的第 j j jj jj 个元素,在旋转后,它出现在倒数第 i i ii ii 列的第 j j jj jj 个位置。
m a t r i x [ r o w ] [ c o l ] matrix[row][col] matrix[row][col],在旋转后,它的新位置为 m a t i r x n e w [ c o l ] [ n − r o w − 1 ] matirx_{new}[col][n-row-1] matirxnew[col][n−row−1]
即: m a t r i x n e w [ c o l ] [ n − r o w − 1 ] = m a t r i x [ r o w ] [ c o l ] matrix_{new}[col][n - row - 1] = matrix[row][col] matrixnew[col][n−row−1]=matrix[row][col]
3.2 方法二:原地旋转
3.2.1 如何原地旋转
方法一占用额外内存的原因:
m
a
t
r
i
x
[
c
o
l
]
[
n
−
r
o
w
−
1
]
=
m
a
t
r
i
x
[
r
o
w
]
[
c
o
l
]
matrix[col][n - row - 1] = matrix[row][col]
matrix[col][n−row−1]=matrix[row][col] 操作会覆盖原始矩阵的值,所以需要创建一个相同大小的辅助矩阵
解决办法: 用一个临时变量 t e m p temp temp 暂存 m a t r i x [ c o l ] [ n − r o w − 1 ] matrix[col][n - row - 1] matrix[col][n−row−1] 的值
此时 t e m p temp temp 中保存的 m a t r i x [ c o l ] [ n − r o w − 1 ] matrix[col][n - row - 1] matrix[col][n−row−1] 的值就可以更新至 m a t i r x [ n − r o w − 1 ] [ n − c o l − 1 ] matirx[n - row - 1][n - col - 1] matirx[n−row−1][n−col−1]
即: m a t r i x [ n − r o w − 1 ] [ n − c o l − 1 ] = m a t r i x [ c o l ] [ n − r o w − 1 ] matrix[n - row - 1][n - col - 1]= matrix[col][n - row - 1] matrix[n−row−1][n−col−1]=matrix[col][n−row−1]
综上:
【注意:考虑第一次直接赋值】
t e m p = m a t r i x [ c o l ] [ n − r o w − 1 ] temp = matrix[col][n - row - 1] temp=matrix[col][n−row−1]
m a t r i x [ c o l ] [ n − r o w − 1 ] = m a t r i x [ r o w ] [ c o l ] matrix[col][n - row - 1] = matrix[row][col] matrix[col][n−row−1]=matrix[row][col]
同样地,直接赋值会覆盖掉
m
a
t
r
i
x
[
n
−
r
o
w
−
1
]
[
n
−
c
o
l
−
1
]
matrix[n - row - 1][n - col - 1]
matrix[n−row−1][n−col−1] 原来的值
解决办法:再次用临时变量
t
e
m
p
temp
temp 暂存
m
a
t
r
i
x
[
n
−
r
o
w
−
1
]
[
n
−
c
o
l
−
1
]
matrix[n - row - 1][n - col - 1]
matrix[n−row−1][n−col−1] 的值
此时 t e m p temp temp 中保存的 m a t r i x [ n − r o w − 1 ] [ n − c o l − 1 ] matrix[n−row−1][n−col−1] matrix[n−row−1][n−col−1] 的值就可以更新至 m a t r i x [ n − c o l − 1 ] [ n − ( n − r o w − 1 ) − 1 ] = m a t r i x [ n − r o l − 1 ] [ r o w ] matrix[n-col-1][n-(n-row-1)-1]=matrix[n-rol-1][row] matrix[n−col−1][n−(n−row−1)−1]=matrix[n−rol−1][row]
即: m a t r i x [ n − r o w − 1 ] [ n − c o l − 1 ] = m a t r i x [ c o l ] [ n − r o w − 1 ] matrix[n - row - 1][n - col - 1]= matrix[col][n - row - 1] matrix[n−row−1][n−col−1]=matrix[col][n−row−1]
综上:
【注意:考虑第二次直接赋值】
t e m p = m a t r i x [ n − r o w − 1 ] [ n − c o l − 1 ] temp = matrix[n - row - 1][n - col - 1] temp=matrix[n−row−1][n−col−1]
m a t r i x [ n − r o w − 1 ] [ n − c o l − 1 ] = m a t r i x [ c o l ] [ n − r o w − 1 ] matrix[n−row−1][n−col−1]=matrix[col][n−row−1] matrix[n−row−1][n−col−1]=matrix[col][n−row−1]
m a t r i x [ c o l ] [ n − r o w − 1 ] = m a t r i x [ r o w ] [ c o l ] matrix[col][n - row - 1] = matrix[row][col] matrix[col][n−row−1]=matrix[row][col]
同样地,直接赋值会覆盖掉
m
a
t
r
i
x
[
n
−
c
o
l
−
1
]
[
r
o
w
]
matrix[n-col-1][row]
matrix[n−col−1][row] 原来的值
解决办法:再次用临时变量
t
e
m
p
temp
temp 暂存
m
a
t
r
i
x
[
n
−
c
o
l
−
1
]
[
r
o
w
]
matrix[n-col-1][row]
matrix[n−col−1][row] 的值
此时 t e m p temp temp 中保存的 m a t r i x [ n − c o l − 1 ] [ r o w ] matrix[n-col-1][row] matrix[n−col−1][row] 的值就可以更新至 m a t r i x [ r o w ] [ n − ( n − c o l − 1 ) − 1 ] = m a t r i x [ r o w ] [ c o l ] matrix[row][n-(n-col-1)-1]=matrix[row][col] matrix[row][n−(n−col−1)−1]=matrix[row][col]
即: m a t r i x [ r o w ] [ c o l ] = m a t r i x [ n − c o l − 1 ] [ r o w ] ] matrix[row][col]= matrix[n-col-1][row]] matrix[row][col]=matrix[n−col−1][row]]
综上:
【注意:考虑第三次直接赋值,回到了最初的起点,实现了闭环!!!】
t e m p = m a t r i x [ n − c o l − 1 ] [ r o w ] temp = matrix[n-col-1][row] temp=matrix[n−col−1][row]
m a t r i x [ n − c o l − 1 ] [ r o w ] = m a t r i x [ n − r o w − 1 ] [ n − c o l − 1 ] matrix[n-col-1][row]= matrix[n - row - 1][n - col - 1] matrix[n−col−1][row]=matrix[n−row−1][n−col−1]
m a t r i x [ n − r o w − 1 ] [ n − c o l − 1 ] = m a t r i x [ c o l ] [ n − r o w − 1 ] matrix[n−row−1][n−col−1]=matrix[col][n−row−1] matrix[n−row−1][n−col−1]=matrix[col][n−row−1]
m a t r i x [ c o l ] [ n − r o w − 1 ] = m a t r i x [ r o w ] [ c o l ] matrix[col][n - row - 1] = matrix[row][col] matrix[col][n−row−1]=matrix[row][col]
m a t r i x [ r o w ] [ c o l ] = t e m p matrix[row][col] = temp matrix[row][col]=temp
即:
m a t r i x [ r o w ] [ c o l ] = m a t r i x [ n − c o l − 1 ] [ r o w ] matrix[row][col] = matrix[n-col-1][row] matrix[row][col]=matrix[n−col−1][row]
m a t r i x [ n − c o l − 1 ] [ r o w ] = m a t r i x [ n − r o w − 1 ] [ n − c o l − 1 ] matrix[n-col-1][row]= matrix[n - row - 1][n - col - 1] matrix[n−col−1][row]=matrix[n−row−1][n−col−1]
m a t r i x [ n − r o w − 1 ] [ n − c o l − 1 ] = m a t r i x [ c o l ] [ n − r o w − 1 ] matrix[n−row−1][n−col−1]=matrix[col][n−row−1] matrix[n−row−1][n−col−1]=matrix[col][n−row−1]
m a t r i x [ c o l ] [ n − r o w − 1 ] = m a t r i x [ r o w ] [ c o l ] matrix[col][n - row - 1] = matrix[row][col] matrix[col][n−row−1]=matrix[row][col]
3.2.2 哪些位置需要原地旋转
情况一:当
n
n
n 为偶数时,我们需要枚举
n
2
/
4
=
(
n
/
2
)
×
(
n
/
2
)
n^2 / 4 = (n/2)×(n/2)
n2/4=(n/2)×(n/2) 个位置,可以将该图形分为四块
情况二:当 n为奇数时,由于中心的位置经过旋转后位置不变,我们需要枚举
(
n
2
−
1
)
/
4
=
(
(
n
−
1
)
/
2
)
×
(
(
n
+
1
)
/
2
)
(n^2-1) / 4 = ((n−1)/2)×((n+1)/2)
(n2−1)/4=((n−1)/2)×((n+1)/2)个位置,需要换一种划分的方式
3.3 方法三:用翻转代替旋转
先将其通过水平轴翻转得到:
再根据主对角线翻转得到:
对于水平轴翻转而言,我们只需要枚举矩阵上半部分的元素,和下半部分的元素进行交换,即
对于主对角线翻转而言,我们只需要枚举对角线左侧的元素,和右侧的元素进行交换,即
将它们联立即可得到:
4. 提交代码
4.1 方法一:使用辅助数组(占用了额外空间)
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
N = len(matrix)
matrix_new = [[0] * N for _ in range(N)] # 创建辅助矩阵
for i in range(N):
for j in range(N):
matrix_new[j][N - i - 1] = matrix[i][j]
matrix[:] = matrix_new
4.2 方法二:原地旋转
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
n = len(matrix)
for i in range(n // 2):
for j in range((n + 1) // 2):# 注意这一步包含了n为奇数和偶数两种情况
matrix[i][j], matrix[n - j - 1][i], matrix[n - i - 1][n - j - 1], matrix[j][n - i - 1] \
= matrix[n - j - 1][i], matrix[n - i - 1][n - j - 1], matrix[j][n - i - 1], matrix[i][j]
4.3 方法三:用翻转代替旋转
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
n = len(matrix)
# 水平翻转
for i in range(n // 2):
for j in range(n):
matrix[i][j], matrix[n - i - 1][j] = matrix[n - i - 1][j], matrix[i][j]
# 主对角线翻转
for i in range(n):
for j in range(i):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
5. 复杂度分析
5.1 方法一:使用辅助数组
时间复杂度:
O
(
N
2
)
O(N^2)
O(N2),其中
N
N
N 是
m
a
t
r
i
x
matrix
matrix 的边长。
空间复杂度:
O
(
N
2
)
O(N^2)
O(N2)。我们需要使用一个和
m
a
t
r
i
x
matrix
matrix 大小相同的辅助数组。
5.2 方法二:原地旋转
时间复杂度:
O
(
N
2
)
O(N^2)
O(N2), 其中
N
N
N 是
m
a
t
r
i
x
matrix
matrix 的边长。我们需要枚举的子矩阵大小为
O
(
N
2
)
⌊
n
/
2
⌋
×
⌊
(
n
+
1
)
/
2
⌋
)
=
O
(
N
2
)
O(N^2)⌊n/2⌋×⌊(n+1)/2⌋)=O(N^2)
O(N2)⌊n/2⌋×⌊(n+1)/2⌋)=O(N2)
空间复杂度:
O
(
1
)
O(1)
O(1),为原地旋转。
5.3 方法三:用翻转代替旋转
时间复杂度:
O
(
N
2
)
O(N^2)
O(N2), 其中
N
N
N 是
m
a
t
r
i
x
matrix
matrix 的边长。对于每一次翻转操作,我们都需要枚举矩阵中一半的元素。
空间复杂度:
O
(
1
)
O(1)
O(1),为原地旋转。
6. 总结
6.1 引用
matrix = matrix_new # 实际上是引用
- 函数里的matrix不再是实参的地址了,而是matrix_new的一个引用,即使修改了,也不会影响实参。
6.2 浅复制
matrix[:] = matrix_new # 修改原来内存中的值
- 在调用函数时,传递给形参的是实参matrix的地址,用切片复制的方式可以根据这个地址对实参进行原地修改
- 当列表对象有嵌套的时候也会产生出乎意料的错误
a = [0, [1, 2], 3]
b = a[:]
a[0] = 8
a[1][1] = 9
print(a)
print(b)
代码运行结果:
[8, [1, 9], 3]
[0, [1, 9], 3]
b 的第二个元素也被改变
6.3 深复制
import copy
a = [0, [1, 2], 3]
b = copy.deepcopy(a)
a[0] = 8
a[1][1] = 9
print(a)
print(b)
代码运行结果:
[8, [1, 9], 3]
[0, [1, 2], 3]