我想到了逆时针动 matrix 而不动指针,我知道 zip 可以转置矩阵,但是我却没有写出 one-liner 😣
leetcode#54. Spiral Matrix
【描述】:
Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral order.
Example 1:
Input:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
Output: [1,2,3,6,9,8,7,4,5]
Example 2:
Input:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]
Output: [1,2,3,4,8,12,11,10,9,5,6,7]
【分析】:
如果使用数组下标(指针)硬淦,那这道题就是很费时的 medium 题。
但是如果能够发现螺旋遍历二维数组的规律,这道题其实和 Easy 程度相差无几。
规律便是我们不动数组下标(不使用下标螺旋遍历),而通过逆时针翻转 90 二维数组然后读取第一行,以此来实现很简单遍历第一行。
逆时针翻转 90 度,遍历过程如下:
|1 2 3| |6 9| |8 7| |4| => |5| => ||
|4 5 6| => |5 8| => |5 4| => |5|
|7 8 9| |4 7|
(1) (2) (3) (4)
先遍历第一行;然后不使用下标的方式向下遍历。
而是对除了已经遍历过的第一行外的,剩下的二维数组,逆时针旋转 90 度,这样旋转过后的数组(即 (2)),第一行就是原先要向下遍历的一排。
遍历完[6,9]
之后,要遍历[8, 7]
显然重复上面逆时针旋转 90 度过程,再遍历第一行即可。
【Solution】:
该解法依赖逆时针旋转 90 度矩阵的辅助函数,完整代码实现如下:
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if len(matrix) == 0 or len(matrix[0]) == 0: return []
ans = [i for i in matrix[0]]
nexT_matrix = rotate_antclockwise(matrix[1:])
while nexT_matrix:
for i in nexT_matrix[0]:
ans.append(i)
nexT_matrix = rotate_antclockwise(nexT_matrix[1:])
return ans
def rotate_antclockwise(matrix):
if not matrix: return []
dst = []
for i in range(len(matrix[0])-1, 0-1, -1): # col: <-<-
dst.append([matrix[j][i] for j in range(len(matrix))])
return dst
上面代码细节很丰富,我想经过分析之后,再看代码没有什么不好理解的。
一次 submission 即过,也没有什么问题:
One-Liner
实际上上面代码可以进一步简化,可以省去很多临时变量之类的东西。
不过首先从逆时针旋转矩阵 90° 开始。
在说逆时针旋转矩阵 90° 之前,我们先看看 one-liner 转置矩阵1:
如果 zip(*array) 不经过 x,y,z=zip(*array); 拆分成 x,y,z 三个变量,那么 [[1,4,7],[2,5,8],[3,6,9]]; 被 zip(*array) 之后的结果恰好是 [(1, 2, 3), (4, 5, 6), (7, 8, 9)],刚好形成一个转置的关系。这对于所有 array=[[1,2,3],[4,5,6],[7,8,9]]; 的二维数组都是一样的。
当然 [(1, 2, 3), (4, 5, 6), (7, 8, 9)] 还不是我们需要的最后的结果。
因为只是一个存放 tuple 的 list,我们要保持原来 list 是存 list 的一致性,所以要应用到上方的 map 函数。
因此对于一个数组的转置,代码如下:
>>> array = [[1, 4], [2, 5], [3, 6]];
>>> map(list, zip(*array));
[[1, 2, 3], [4, 5, 6]]
zip 返回的是惰性迭代的
<tuple>
类型。
如果最终结构需要 二维 list 类型,就需要map(list, <zip>)
转换。
如果转置只是中间过程,那么没有map(list, <zip>)
也可以。
从转置矩阵到逆时针旋转90°:
如果仔细观察上面的转置矩阵,就会发现,将转置矩阵按行 reverse,就会得到原矩阵逆时针旋转90°的结果:
[[1, 4, 7], T [[1, 2, 3], reverse [[7, 8, 9],
[2, 5, 8], ==> [4, 5, 6], ==> [4, 5, 6],
[3, 6, 9]] [7, 8, 9]] [1, 2, 3]]
综上,rotate_antclockwise
函数可以写成一行:
def rotate_antclockwise(matrix):
return list(map(list, zip(*matrix)))[::-1]
当然了,如果不要求返回结果是二维 list 类型,而是中间过程,只要数值是对的即可,那么可以简化写成:
rotated = [*zip(*matrix)][::-1] # <list <tuple>>
简化逆时针旋转辅助函数
简化过后的 solution 可以写成:
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if len(matrix) == 0 or len(matrix[0]) == 0: return []
ans = [i for i in matrix[0]]
nexT_matrix = rotate_antclockwise(matrix[1:])
while nexT_matrix:
for i in nexT_matrix[0]:
ans.append(i)
nexT_matrix = rotate_antclockwise(nexT_matrix[1:])
return ans
def rotate_antclockwise(matrix):
return [*zip(*matrix)][::-1]
递归简化
对于上面 nexT_matrix
+ while
循环遍历的过程可以简化为递归:
fn: matrix[0] + fn(antclockwise-rotate matrix[1:])
||
\/
fn: matrix[1] + fn(antclockwise-rotate matrix[2:])
...
fn: matrix[N-1] + fn(antclockwise-rotate matrix[N:])
\/ fn([]) # N = len(matrix)
fn: return []
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
return matrix if not matrix else recursive(matrix)
def recursive(matrix):
return matrix if not matrix else [*matrix.pop(0)] + recursive([*zip(*matrix)][::-1])
one-liner
上面递归函数可以直接使用 spiralOrder
函数,不需要再创建一个 recursive
函数:
def spiralOrder(self, matrix):
return matrix and [*matrix.pop(0)] + self.spiralOrder([*zip(*matrix)][::-1])
注意 and 在 Python 中代替三元运算表达式的用法。
a and b
不论结果如何,都返回a
或b
本身。