本文中所涉及的代码,在未特殊声明的情况下,都是基于Python3程序设计语言编写的。
建议您在PC浏览器中阅读本文,以获得更好的阅读体验。
0
问题描述
实现一个函数,该函数接收一个n×n二维矩阵matrix,将该矩阵顺时针旋转90度。要求直接对参数matrix进行修改,函数不返回任务东西。
例如:
给定 matrix =
[
[1, 2],
[3, 4],
]
旋转后为matrix=
[
[4, 1],
[3, 2],
]
给定matrix=
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
旋转后matrix=
[
[7, 4, 1],
[8, 5, 2],
[9, 6, 3],
]
在力扣上可以找到相同的题目,叫《旋转图像》,其官方题解中,给出了本文中最后一种(第四种)解法的两种实现方式,感兴趣的读者可以去了解一下。
本文中讨论到坐标时,都是以Python语法为参照的,即坐标是从0开始算的。由于旋转操作涉及到移动每一个元素,所以本题目的所有解法的时间复杂度都是O(N^2),也就是至少遍历一次matrix。
对于矩阵的一次置换操作,如果连续做两遍,矩阵又回到了原来的样子,我们称这个置换是可逆的;否则,我们称这个置换是不可逆的。
1
公式法
对矩阵进行顺时针90度旋转,相当于把每个坐标(r, c)的元素移动到(tr, tc)上,这两个坐标满足如下的转转换关系:
tr = c
tc = n- r - 1
需要注意的是,上面的转换操作是不可逆的,比如A→B, B→C,A和C是不同的元素。当我们把A移动到B的时候,B的值被改成了A的值。所以我们必须先记录B的值,再做B→C的操作。同样,做B→C时,必须先记录C的值。实际上,我们并没有办法动态记录这些信息,所以我们只能先拷贝一份matrix,然后借助这份拷贝来直接对matrix进行操作。所以,这种解法遍历了两次matrix,拷贝一次,转换一次;而空间复杂度则为O(N^2)。
def rotate(matrix):
n = len(matrix)
# 复制matrix
copy = [[matrix[r][c] for c in range(n)] for r in range(n)]
# 转换公式:源坐标(r, c),目标坐标(tr, tc)
# tr = c, tc = n-r-1
for r in range(n):
for c in range(n):
tr = c
tc = n - r - 1
matrix[tr][tc] = copy[r][c]
2
二次置换法
将矩阵顺时针旋转90度,可以通过二次置换得到:先将矩阵倒置,再将倒置后的矩阵的每一个行的元素顺序倒置。
因为这两种置换操作都是可逆的,所以通过这个方式,空间复杂为O(1);而这种方式也是遍历两次matrix,遍历次数与第一种解法的一样。
def rotate(matrix):
n = len(matrix)
# 矩阵倒置,即(r,c)->(c,r)
for r in range(n):
for c in range(r, n):
matrix[r][c], matrix[c][r] = matrix[c][r], matrix[r][c]
# 每一行倒转
for r in range(n):
# 以下两行相当于:matrix[r] = matrix[r][::-1]
# 本着不修改matrix内部结构的原则,用下面的方式
for c in range(n // 2):
matrix[r][c], matrix[r][n - 1 - c] = matrix[r][n - 1 - c], matrix[r][c]
3
边整体旋转法
对于行列数都为n的矩阵,它总共有ceil(n / 2)圈(ceil表示向上取整)。我们可以通过对每一圈的四条边进行顺时针旋转来实现总体的效果。在边旋转过程中,我们需要先临时记录其中一条边的值,以便在最后将其放到目标的位置。所以边旋转法的空间空间复杂度为O(N),而且只对matrix遍历了一次。
每条边从起始位置旋转到目标位置的坐标转换公式,各位读者请自行推导,本文将不对此进行详细解说。
def rotate(matrix):
n = len(matrix)
mi = n - 1 # 最大索引
# 每一圈loop,对四条边单独旋转
for loop in range(n // 2):
# 每一圈边长
length = mi - 2 * loop
# 将左边存储到临时变量left中
left_c = loop
left_r_start = loop
left = [matrix[left_r_start + i][left_c] for i in range(length)]
# 将底边存到左边
bottom_c_start = loop
bottom_r = mi - loop
for i in range(length):
matrix[left_r_start + i][left_c] = matrix[bottom_r][bottom_c_start + i]
# 将右边存到底边
right_c = mi - loop
right_r_start = mi - loop
for i in range(length):
matrix[bottom_r][bottom_c_start + i] = matrix[right_r_start - i][right_c]
# 将上边存到右边
top_r = loop
top_c_start = mi - loop
for i in range(length):
matrix[right_r_start - i][right_c] = matrix[top_r][top_c_start - i]
# 将左边,即left存到上边
for i in range(length):
matrix[top_r][top_c_start - i] = left[i]
4
边元素依次旋转法
事实上,我们可以对每一圈的每条边上的每个元素单独进行旋转,这样的话,我们只需要临时记录其中的一个元素,以达到空间复杂度为O(1)的实现。
边元素旋转过程如图所示:
def rotate(matrix):
n = len(matrix)
mi = n - 1 # 最大索引
# 每一圈loop
for loop in range(n // 2):
# 每一圈边长
length = mi - 2 * loop
# 分别对边上每四个对应的元素进行旋转
for i in range(length):
# bottom->left, right->bottom, top->right, left->top
left = matrix[loop + i][loop]
matrix[loop + i][loop] = matrix[mi - loop][loop + i]
matrix[mi - loop][loop + i] = matrix[mi - loop - i][mi - loop]
matrix[mi - loop - i][mi - loop] = matrix[loop][mi - loop - i]
matrix[loop][mi - loop - i] = left微信扫码关注我哦