力扣-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, 最开始弄反了, 导致我弄了好久没弄出来!!!
这里关键点是知道一条对角线触碰到边界后, 下一次的起始点!!! 通过下图我们能发现规律
- 如果上一次是向右上截止(本次是左下方向), 那么优先级是首先找是否有(x,y+1), 如果没有选择(x+1, y)
- 如果上一次是向左下截止(本次是右上方向), 那么优先级是首先找是否有(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)\)