力扣-498. 对角线遍历

1.题目

题目地址(498. 对角线遍历 - 力扣(LeetCode))

https://leetcode.cn/problems/diagonal-traverse/

题目描述

给你一个大小为 m x n 的矩阵 mat ,请以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。

 

示例 1:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,4,7,5,3,6,8,9]

示例 2:

输入:mat = [[1,2],[3,4]]
输出:[1,2,3,4]

 

提示:

  • m == mat.length
  • n == mat[i].length
  • 1 <= m, n <= 104
  • 1 <= m * n <= 104
  • -105 <= mat[i][j] <= 105

2.题解

2.1 模拟(简单图论)

注意一下向右是y+1, 向下是x+1, 最开始弄反了, 导致我弄了好久没弄出来!!!
这里关键点是知道一条对角线触碰到边界后, 下一次的起始点!!! 通过下图我们能发现规律

  1. 如果上一次是向右上截止(本次是左下方向), 那么优先级是首先找是否有(x,y+1), 如果没有选择(x+1, y)
  2. 如果上一次是向左下截止(本次是右上方向), 那么优先级是首先找是否有(x+1,y), 如果没有选择(x, y+1)

对角线方向遍历, 我们设置一个方向数组dir即可{{-1, 1}(右上), {1, -1}(左下)}
对于碰壁后出发点转变, 我们新设置一个出发点变更数组 trans(按上方的优先级即可) 即可; 这里要判断两次, 第一次是判断按对角线方向走是否碰壁, 第二次是判断高优先级出发点是否存在,不存在就选择次级优先级出发点.

代码

  • 语言支持:C++

C++ Code:

class Solution {
	public:
		vector<int> findDiagonalOrder(vector<vector<int>>& mat) {
			int row = mat.size(), col = mat[0].size();
			vector<pair<int, int>> dir{{-1, 1},{1, -1}}; // 分别对应右上, 左下方向
			vector<pair<int, int>> trans{{0, 1},{1, 0}}; // cnt=0(右上方向时,下次左下), 优先{0, 1}; cnt=1(左上方向时,下次右上), 优先 {1, 0}; (cnt+1)%2表示次级优先
			vector<int> ans;
			int cnt = 0, x = 0, y = 0;
			for(int i = 0; i < row * col; i++) {
				ans.push_back(mat[x][y]);
                // 按对角线方向行走
				int dx = x + dir[cnt].first; 
				int dy = y + dir[cnt].second;
                // 按对角线走碰壁
				if(dx < 0 || dx >= row || dy < 0 || dy >= col) {
					dx = x + trans[cnt].first;
					dy = y + trans[cnt].second;
                    // 高优先级出发点不存在, 选择次级优先级出发点
					if(dx < 0 || dx >= row || dy < 0 || dy >= col) {
						dx = x + trans[(cnt+1)%2].first;
						dy = y + trans[(cnt+1)%2].second;
					}
					cnt = (cnt+1) % 2; // 转变对角线行走方向
				}
                // 更新 x, y值
				x = dx;
				y = dy;
			}
			return ans;
		}
};

复杂度分析

令 n 为数组长度。

  • 时间复杂度:\(O(m * n)\)
  • 空间复杂度:\(O(1)\)

2.2 模拟(对角线)

思路

一共有 m + n - 1 条对角线,相邻的对角线的遍历方向不同,当前遍历方向为从左下到右上,则紧挨着的下一条对角线遍历方向为从右上到左下;
设对角线从上到下的编号为 i∈[0, m + n − 2]:
当 i 为偶数时,则第 i 条对角线的走向是从下往上遍历;
当 i 为奇数时,则第 i 条对角线的走向是从上往下遍历;

实际上:
当第 i 条对角线从下往上遍历时,每次行索引减 1,列索引加 1,直到矩阵的边缘为止:
当 i < m 时,则此时对角线遍历的起点位置为 (i, 0);
当 i ≥ m 时,则此时对角线遍历的起点位置为 (m - 1, i - m + 1);

当第 i 条对角线从上往下遍历时,每次行索引加 1,列索引减 1,直到矩阵的边缘为止:
当 i < n 时,则此时对角线遍历的起点位置为 (0, i);
当 i ≥ n 时,则此时对角线遍历的起点位置为 (i - n + 1, n - 1);

可以对照下图, 我们可以看到对角线起点都是想要优先构成一个正方形的;
也就是说对于一个向右上的对角线,我们可以从第一个向右上的起点向下平移到一个偶数次起点;
如果有(i < m),从该点开始向右上遍历(i, 0); 如果没有(i ≥ m, 但是最大行下标为 m - 1), 沿着该对角线直到找到第一个存在的起点 (m - 1, i - m + 1) (这里也就是最后一行) [(i, 0) 和 (m - 1, i - m + 1) 在一条对角线上!]
左上同理!

代码

class Solution {
public:
    vector<int> findDiagonalOrder(vector<vector<int>>& mat) {
        int m = mat.size();
        int n = mat[0].size();
        vector<int> res;
        for (int i = 0; i < m + n - 1; i++) {
            // 奇数就是左下方向(i % 2 != 0)
            if (i % 2) {
                int x = i < n ? 0 : i - n + 1;
                int y = i < n ? i : n - 1;
                // 遍历完一条对角线上所有元素
                while (x < m && y >= 0) {
                    res.emplace_back(mat[x][y]);
                    x++;
                    y--;
                }
            // 偶数就是右上方向
            } else {
                int x = i < m ? i : m - 1;
                int y = i < m ? 0 : i - m + 1;
                // 遍历完一条对角线上所有元素
                while (x >= 0 && y < n) {
                    res.emplace_back(mat[x][y]);
                    x--;
                    y++;
                }
            }
        }
        return res;
    }
};

复杂度分析

令 n 为数组长度。

  • 时间复杂度:\(O(m * n)\)
  • 空间复杂度:\(O(1)\)
posted @ 2024-04-27 16:52  DawnTraveler  阅读(28)  评论(0编辑  收藏  举报