数据结构与算法之深入解析“螺旋矩阵”的求解思路与算法示例

Posted Forever_wj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法之深入解析“螺旋矩阵”的求解思路与算法示例相关的知识,希望对你有一定的参考价值。

一、题目要求

  • 给你一个 m 行 n 列的矩阵 matrix ,请按照顺时针螺旋顺序 ,返回矩阵中的所有元素。
  • 示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
  • 示例 2:

输入: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]
  • 提示:
    • m == matrix.length
    • n == matrix[i].length
    • 1 <= m, n <= 10
    • -100 <= matrix[i][j] <= 100

二、求解算法

① 按圈遍历

  • 从外部向内部逐层遍历打印矩阵,最外面一圈打印完,里面仍然是一个矩阵。
  • 统计矩阵的层数,每一层最多会占据两行或者两列,最少会占据一行或者一列元素,只有一层或者一列,也算一层,分层如下图:

int m = matrix.length;
int n = matrix[0].length;
int count = (Math.min(m, n)+1)/2;
  • 开始打印第 i 层的矩阵元素,如下图所示,再打印第 i 层的矩阵时,要经历 4 个循环:

  • 第 1 个:从左向右
for (int j = i; j < n-i; j++) 
    list.add(matrix[i][j]);
 
  • 第 2 个:从上往下
for (int j = i+1; j < m-i; j++) 
    list.add(matrix[j][(n-1)-i]);

  • 第 3 个:从右往左,如果这一层只有1行,那么第一个循环已经将该行打印,这里就不需要打印了,即 (m-1-i )!= i:
for (int j = (n-1)-(i+1); j >= i && (m-1-i != i); j--) 
    list.add(matrix[(m-1)-i][j]);

  • 第4个:从下往上,如果这一层只有1列,那么第2个循环已经将该列打印了,这里不需要打印,即(n-1-i) != i
for (int j = (m-1)-(i+1); j >= i+1 && (n-1-i) != i; j--) 
    list.add(matrix[j][i]);

  • Java 示例:
public List<Integer> spiralOrder(int[][] matrix) 
        List<Integer> list = new ArrayList<Integer>();
        if(matrix == null || matrix.length == 0)
    		return list;
        int m = matrix.length;
        int n = matrix[0].length;
        int i = 0; 

        // 统计矩阵从外向内的层数,如果矩阵非空,那么它的层数至少为1层
        int count = (Math.min(m, n)+1)/2;
        // 从外部向内部遍历,逐层打印数据
        while(i < count) 
        	for (int j = i; j < n-i; j++) 
				list.add(matrix[i][j]);
			
        	for (int j = i+1; j < m-i; j++) 
				list.add(matrix[j][(n-1)-i]);
			
        	
        	for (int j = (n-1)-(i+1); j >= i && (m-1-i != i); j--) 
				list.add(matrix[(m-1)-i][j]);
			
        	for (int j = (m-1)-(i+1); j >= i+1 && (n-1-i) != i; j--) 
				list.add(matrix[j][i]);
			
        	i++;
            
        return list;
    
  • C++ 示例:
class Solution 
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) 
        int sizey = matrix.size();
        int sizex = matrix[0].size();
        // 遍历圈数
        int loop = min(sizey, sizex) / 2;
        // 遍历每个圈的起始位置
        int starty = 0, startx = 0;
        // 每个圈中,Y 轴和 X 轴遍历个数
        int leny = sizey - 1, lenx = sizex - 1;
        vector<int> ans;
        // 单行矩阵
        if(sizey == 1) 
            for(int x = startx, y = starty; x < startx + lenx + 1; x ++) 
                ans.emplace_back(matrix[y][x]);
            
            return ans;
        
        // 单列矩阵
        if(sizex == 1) 
            for(int x = startx, y = starty; y < starty + leny + 1; y ++) 
                ans.emplace_back(matrix[y][x]);
            
            return ans;
        

        while(loop --) 
            for(int x = startx, y = starty; x < startx + lenx; x ++) 
                ans.emplace_back(matrix[y][x]);
            
            for(int x = startx + lenx, y = starty; y < starty + leny; y ++) 
                ans.emplace_back(matrix[y][x]);
            
            for(int x = startx + lenx, y = starty + leny; x > startx; x --) 
                ans.emplace_back(matrix[y][x]);
            
            for(int x = startx, y = starty + leny; y > starty; y --) 
                ans.emplace_back(matrix[y][x]);
            

            starty ++;
            startx ++;
            leny -= 2;
            lenx -= 2;
        
        // 最后一圈是一行
        if(sizey <= sizex && sizey % 2 == 1) 
            for(int x = startx, y = starty; x < startx + lenx + 1; x ++) 
                ans.emplace_back(matrix[y][x]);
            
        
        // 最后一圈是一列
        if(sizey > sizex && sizex % 2 == 1) 
            for(int x = startx, y = starty; y < starty + leny + 1; y ++) 
                ans.emplace_back(matrix[y][x]);
            
        

        return ans;
    
;

② 模拟

  • 可以模拟螺旋矩阵的路径。初始位置是矩阵的左上角,初始方向是向右,当路径超出界限或者进入之前访问过的位置时,顺时针旋转,进入下一个方向。
  • 判断路径是否进入之前访问过的位置需要使用一个与输入矩阵大小相同的辅助矩阵 visited,其中的每个元素表示该位置是否被访问过。当一个元素被访问时,将 visited 中的对应位置的元素设为已访问。
  • 如何判断路径是否结束?由于矩阵中的每个元素都被访问一次,因此路径的长度即为矩阵中的元素数量,当路径的长度达到矩阵中的元素数量时即为完整路径,将该路径返回。
  • Java 示例:
class Solution 
    public List<Integer> spiralOrder(int[][] matrix) 
        List<Integer> order = new ArrayList<Integer>();
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) 
            return order;
        
        int rows = matrix.length, columns = matrix[0].length;
        boolean[][] visited = new boolean[rows][columns];
        int total = rows * columns;
        int row = 0, column = 0;
        int[][] directions = 0, 1, 1, 0, 0, -1, -1, 0;
        int directionIndex = 0;
        for (int i = 0; i < total; i++) 
            order.add(matrix[row][column]);
            visited[row][column] = true;
            int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];
            if (nextRow < 0 || nextRow >= rows || nextColumn < 0 || nextColumn >= columns || visited[nextRow][nextColumn]) 
                directionIndex = (directionIndex + 1) % 4;
            
            row += directions[directionIndex][0];
            column += directions[directionIndex][1];
        
        return order;
    

  • C++ 示例:
class Solution 
private:
    static constexpr int directions[4][2] = 0, 1, 1, 0, 0, -1, -1, 0;
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) 
        if (matrix.size() == 0 || matrix[0].size() == 0) 
            return ;
        
        
        int rows = matrix.size(), columns = matrix[0].size();
        vector<vector<bool>> visited(rows, vector<bool>(columns));
        int total = rows * columns;
        vector<int> order(total);

        int row = 0, column = 0;
        int directionIndex = 0;
        for (int i = 0; i < total; i++) 
            order[i] = matrix[row][column];
            visited[row][column] = true;
            int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];
            if (nextRow < 0 || nextRow >= rows || nextColumn < 0 || nextColumn >= columns || visited[nextRow][nextColumn]) 
                directionIndex = (directionIndex + 1) % 4;
            
            row += directions[directionIndex][0];
            column += directions[directionIndex][1];
        
        return order;
    
;
  • 复杂度分析:
    • 时间复杂度:O(mn),其中 m 和 n 分别是输入矩阵的行数和列数。矩阵中的每个元素都要被访问一次。
    • 空间复杂度:O(mn)。需要创建一个大小为 m×n 的矩阵 visited 记录每个位置是否被访问过。

③ 按层模拟(LeetCode 官方解法)

  • 可以将矩阵看成若干层,首先输出最外层的元素,其次输出次外层的元素,直到输出最内层的元素。
  • 定义矩阵的第 k 层是到最近边界距离为 k 的所有顶点。例如,如下矩阵最外层元素都是第 1 层,次外层元素都是第 2 层,剩下的元素都是第 3 层:
[[1, 1, 1, 1, 1, 1, 1],
 [1, 2, 2, 2, 2, 2, 1],
 [1, 2, 3, 3, 3, 2, 1],
 [1, 2, 2, 2, 2, 2, 1],
 [1, 1, 1, 1, 1, 1, 1]]
  • 对于每层,从左上方开始以顺时针的顺序遍历所有元素。假设当前层的左上角位于 (top,left),右下角位于 (bottom,right),按照如下顺序遍历当前层的元素。
    • 从左到右遍历上侧元素,依次为 (top,left) 到 (top,right)。
    • 从上到下遍历右侧元素,依次为 (top+1,right) 到 (bottom,right)。
    • 如果 left<right 且 top<bottom,则从右到左遍历下侧元素,依次为 (bottom,right−1) 到 (bottom,left+1),以及从下到上遍历左侧元素,依次为 (bottom,left) 到 (top+1,left)。
  • 遍历完当前层的元素之后,将 left 和 top 分别增加 1,将 right 和 bottom 分别减少 1,进入下一层继续遍历,直到遍历完所有元素为止。

  • Java 示例:
class Solution 
    public List<Integer> spiralOrder(int[][] matrix) 
        List<Integer> order = new ArrayList<Integer>();
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) 
            return order;
        
        int rows = matrix.length, columns = matrix[0].length;
        int left = 0, right = columns - 1, top = 0, bottom = rows - 1;
        while (left <= right && top <= bottom)以上是关于数据结构与算法之深入解析“螺旋矩阵”的求解思路与算法示例的主要内容,如果未能解决你的问题,请参考以下文章

数据结构与算法之深入解析“完美数”的求解思路与算法示例

数据结构与算法之深入解析“股票的最大利润”的求解思路与算法示例

数据结构与算法之深入解析“安装栅栏”的求解思路与算法示例

数据结构与算法之深入解析“最长连续序列”的求解思路与算法示例

数据结构与算法之深入解析“路径总和”的求解思路与算法示例

数据结构与算法之深入解析“斐波那契数”的求解思路与算法示例