Leetcode算法——54、螺旋矩阵(spiral matrix)

本文介绍了一种矩阵螺旋遍历的算法,通过递归法实现矩阵元素的螺旋顺序输出,包括两行代码实现的精妙方法,适用于各种大小的矩阵。

给定一个矩阵 m*n,返回所有元素的螺旋排列顺序(从左上角开始,顺时针旋转,由外向内)。

示例1:

Input:
[
 [ 1, 2, 3 ],
 [ 4, 5, 6 ],
 [ 7, 8, 9 ]
]
Output: [1,2,3,6,9,8,7,4,5]

示例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]

思路

1、递归法

先输出最外圈的顺时针顺序,然后用同样方法处理剩下的元素。

最外圈的顺时针顺序为:
1、最上面一行,从左到右
2、最右面一列,从上到下
3、最下面一行,从右到左
4、最左面一列,从下到上

然后,使用递归法,处理第二圈、第三圈等,直到矩阵最中间的一圈被处理完毕(这也是递归结束条件)。

2、递归法(一行代码)

另外一种递归法:
1、读取最上面一行,从左到右
2、将最上面一行从矩阵中删除
3、将矩阵逆时针旋转90度。
4、递归,继续处理最上面一行。

递归结束条件:矩阵为空。

实际上,这种方法只需要用一行python代码便可以实现:

def spiralOrder2(matrix):
    return matrix and [*matrix.pop(0)] + spiralOrder2([*zip(*matrix)][::-1])

其中,

1、当 matrix = [] 时,matrix and [*matrix.pop(0)] 这个表达式返回 [],这是因为 and 符号连接时,先计算左边的正负,时,matrix 为空,为FALSE,因此不必再计算 and 右边的表达式。直接返回 matrix,即 []。

2、[*zip(*matrix)] 表示将 matrix 的第一列作为第一行,第二列作为第二行… 即将矩阵进行对角线对称。

python语法:

* 后面加上可迭代对象,相当于将可迭代对象依次列出

可以用在两个场合:
1、[*a] 表示将a变为list,相当于 list(a)
2、fun(*a) 表示将a变为list,并且每个元素分别作为fun()的一个参数,即这时 fun 被传入了多个参数,而不是一个。
比如:

    >>> matrix = [1,2,3]
    >>> type(*matrix)
    Traceback (most recent call last):
        File "<ipython-input-283-d63d2bd0f73e>", line 1, in <module>
            type(*matrix)
    TypeError: type.__new__() argument 1 must be str, not int

相当于执行了 type(1,2,3),因此会报错。

3、[*zip(*matrix)][::-1] 表示先将 matrix 进行对角线对称,然后将所有行逆序排列。

比如原矩阵为:

matrix = [
[1,2,3],
[4,5,6]
]

先对角线对称:

matrix2 = [
[1,4],
[2,5],
[3,6]
]

再逆序排列:

matrix3 = [
[3,6],
[2,5]
[1,4]
]

可见, matrix3 是 matrix 逆时针旋转 90 度的结果。

python实现

def spiralOrder(matrix):
    """
    :type matrix: List[List[int]]
    :rtype: List[int]
    递归法。
    先输出最外圈的顺时针顺序,然后用同样方法处理剩下的元素。
    """
    
    # 递归结束条件
    if not matrix or len(matrix[0]) == 0:
        return []
    if len(matrix) == 1: # 只有一行
        return matrix[0]
    if len(matrix[0]) == 1: # 只有一列
        return [x[0] for x in matrix]
    
    # 最外圈的螺旋排列
    result = matrix[0] # 第一行
    result += [x[-1] for x in matrix][1:-1] # 最后一列
    result +=  matrix[-1][::-1] # 最后一行
    result += [x[0] for x in matrix][-2:0:-1]  # 第一列
    
    # 切割出剩余元素
    remain = [matrix[i][1:-1] for i in range(1,len(matrix)-1)]
    
    return result + spiralOrder(remain)

def spiralOrder2(matrix):
    '''
    递归法,一行代码。
    '''
    return matrix and [*matrix.pop(0)] + spiralOrder2([*zip(*matrix)][::-1])

if '__main__' == __name__:
    matrix = [
 [ 1, 2, 3 ],
 [ 4, 5, 6 ],
 [ 7, 8, 9 ]
]
    print(spiralOrder2(matrix))
<think>我们正在处理的是LeetCode54题:螺旋矩阵。给定一个mxn的矩阵,按照螺旋顺序返回矩阵中的所有元素。根据引用内容,我们可以采用模拟螺旋路径的方法,使用方向数组来控制移动方向,当遇到边界或已访问的元素时改变方向。算法步骤:1.定义方向数组DIRS,表示右、下、左、上四个方向(顺时针)。2.初始化一个与输入矩阵同样大小的visited矩阵(或者修改原矩阵标记已访问)?但引用[1]中使用了将访问过的位置标记为Integer.MAX_VALUE的方法,这样可以在原矩阵上操作,避免额外空间。但注意题目要求不能修改原矩阵?题目没有明确,但通常我们应避免修改原数据。不过引用[1]的方法修改了原矩阵,如果要求不修改,则需要额外空间。3.另一种方法是使用边界收缩法(引用[3]提到),但引用[1]的方法更简洁。然而,题目要求返回列表,不要求保留原矩阵,所以我们可以修改原矩阵(但题目没有说可以修改,所以最好不修改?)。但引用[1]的解法修改了原矩阵。我们也可以使用一个visited数组来记录访问过的位置,这样就不修改原矩阵。考虑到题目矩阵大小不超过10x10,使用额外空间是可以接受的。但引用[1]的方法已经比较高效,且代码简洁。这里我们采用引用[1]的方法,但注意题目中矩阵元素可能包含负数,所以标记值不能是Integer.MIN_VALUE?因为题目中元素范围在-100到100,所以我们可以使用一个超出这个范围的值(比如101或Integer.MAX_VALUE)作为标记。但是,题目没有说不能修改原矩阵,所以我们可以使用。但是,如果我们不想修改原矩阵,我们可以使用一个同样大小的boolean数组来记录访问情况。两种方法都可以,这里我们提供两种实现,但根据引用[1],我们先给出修改原矩阵的实现(因为代码简洁),然后再提供一个不修改原矩阵的实现(使用边界收缩法)。不过,引用[3]提到边界收缩法,我们也可以实现。由于问题要求包含输入输出示例,我们将提供:1.算法实现(两种方法)2.输入输出示例方法一:模拟路径(修改原矩阵)-使用方向数组,按顺序向右、向下、向左、向上移动。-当下一步到达矩阵边界或已经访问过的位置时,改变方向(通过取余循环切换方向)。-使用一个变量记录当前方向,每次移动后判断下一步是否需要转向。方法二:边界收缩法(不修改原矩阵)-定义四个边界:上边界top=0,下边界bottom=m-1,左边界left=0,右边界right=n-1。-按照顺时针方向遍历:1.从左到右遍历上边界(top行),遍历完上边界下移(top++)2.从上到下遍历右边界(right列),遍历完右边界左移(right--)3.从右到左遍历下边界(bottom行),遍历完下边界上移(bottom--)4.从下到上遍历左边界(left列),遍历完左边界右移(left++)-循环条件:top<=bottom且left<=right方法二更直观,且不需要修改原矩阵,也不需要额外的标记数组,空间复杂度O(1)(除了输出列表)。我们选择方法二作为主要实现,因为它更符合题目要求(不修改原矩阵)且高效。下面给出Java实现:方法二:边界收缩法步骤:初始化top=0,bottom=m-1,left=0,right=n-1。初始化结果列表ans。while(top<=bottom&&left<=right){1.从左到右:遍历top行的left到right列,将元素加入ans。然后top++。2.从上到下:遍历right列的top到bottom行,将元素加入ans。然后right--。3.检查是否还有行(因为top可能已经超过bottom):从右到左:遍历bottom行的right到left列(逆序),将元素加入ans。然后bottom--。4.检查是否还有列:从下到上:遍历left列的bottom到top行(逆序),将元素加入ans。然后left++。}注意:在第3步和第4步之前,需要检查是否还有行/列,因为可能已经遍历完了(比如只有一行或一列的情况)。具体代码:输入输出示例:输入:matrix=[[1,2,3,4],[5,6,7,8],[9,10,11,12]]输出:[1,2,3,4,8,12,11,10,9,5,6,7]代码实现:</think>### LeetCode 54螺旋矩阵 Java 实现 #### 问题描述 给定一个 m×n 的矩阵,按照顺时针螺旋顺序返回矩阵中的所有元素。 **示例输入**: ```java int[][] matrix = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; ``` **示例输出**: ``` [1,2,3,4,8,12,11,10,9,5,6,7] ``` --- ### 高效实现方案(边界收缩法) ```java import java.util.ArrayList; import java.util.List; class Solution { public List<Integer> spiralOrder(int[][] matrix) { List<Integer> result = new ArrayList<>(); if (matrix == null || matrix.length == 0) return result; int top = 0, bottom = matrix.length - 1; int left = 0, right = matrix[0].length - 1; while (top <= bottom && left <= right) { // 1. 从左到右遍历顶层 for (int j = left; j <= right; j++) { result.add(matrix[top][j]); } top++; // 2. 从上到下遍历右侧 for (int i = top; i <= bottom; i++) { result.add(matrix[i][right]); } right--; // 3. 检查是否仍有行需要处理 if (top <= bottom) { // 从右到左遍历底层 for (int j = right; j >= left; j--) { result.add(matrix[bottom][j]); } bottom--; } // 4. 检查是否仍有列需要处理 if (left <= right) { // 从下到上遍历左侧 for (int i = bottom; i >= top; i--) { result.add(matrix[i][left]); } left++; } } return result; } } ``` --- ### 算法解析 1. **边界初始化**: - `top=0`, `bottom=行数-1` - `left=0`, `right=列数-1` 2. **四步循环**: ```java while (top <= bottom && left <= right) { // 向右遍历 → [top行, left→right] // 向下遍历 ↓ [right列, top→bottom] // 向左遍历 ← [bottom行, right→left] (需检查top<=bottom) // 向上遍历 ↑ [left列, bottom→top] (需检查left<=right) } ``` 3. **边界收缩**: - 每完成一个方向遍历后收缩对应边界 - 例如:完成顶层遍历后 `top++` 4. **条件检查**: - 向左/向上遍历前需检查边界有效性 - 防止单行/单列情况下的重复遍历[^1] --- ### 时间复杂度分析 - **时间复杂度**:$O(m \times n)$ 需访问矩阵中的每个元素恰好一次 - **空间复杂度**:$O(1)$(不考虑输出列表) 仅使用固定数量的指针变量 --- ### 测试用例 ```java public static void main(String[] args) { Solution solution = new Solution(); // 示例测试 int[][] matrix1 = {{1,2,3},{4,5,6},{7,8,9}}; System.out.println(solution.spiralOrder(matrix1)); // 输出: [1,2,3,6,9,8,7,4,5] int[][] matrix2 = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; System.out.println(solution.spiralOrder(matrix2)); // 输出: [1,2,3,4,8,12,11,10,9,5,6,7] // 边界测试 int[][] matrix3 = {{7}}; System.out.println(solution.spiralOrder(matrix3)); // 输出: [7] } ``` --- ### 应用场景 1. **图像处理**:像素矩阵螺旋遍历 2. **游戏开发**:地图探索路径规划 3. **数据压缩**:矩阵数据的螺旋编码 4. **内存管理**:二维存储结构的访问优化[^3] --- ### 相关问题 1. 如何实现**逆时针**螺旋遍历矩阵? 2. 如何处理**非矩形**(如三角形)矩阵螺旋遍历? 3. 如何将螺旋遍历算法扩展**三维矩阵**? 4. 在图像处理中,螺旋遍历如何优化**局部性原理**? 5. 如何修改算法实现**从外向内**和**从内向外**两种螺旋顺序?[^2]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值