【LeetCode 每日一题】498. 对角线遍历——(解法一)模拟
Problem: 498. 对角线遍历
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(M * N)
- 空间复杂度:O(1) (不考虑输出数组)
整体思路
这段代码旨在解决一个二维矩阵的遍历问题:对角线遍历 (Diagonal Traverse)。它要求按照 “Z” 字形或 “蛇形” 的顺序遍历一个 m x n
的矩阵,并将所有元素按此顺序存入一个一维数组中。
该算法采用了一种 模拟(Simulation) 的方法,直接根据规则模拟遍历的路径。它不依赖于复杂的数据结构,而是通过维护当前位置 (i, j)
和一个方向标志 f
来控制整个遍历过程。
其核心逻辑步骤如下:
-
状态维护:
i
,j
: 当前在矩阵中的行和列索引。f
: 一个方向标志。代码中使用f = 1
代表向右上(行索引减小,列索引增大)移动,f = -1
代表向左下(行索引增大,列索引减小)移动。k
: 一个从0
到m*n - 1
的循环变量,确保每个元素都被访问且仅被访问一次。
-
主循环与遍历:
- 算法通过一个
for
循环,总共迭代m * n
次,对应矩阵中的每一个元素。 - 在每次循环的开始,首先将当前位置
mat[i][j]
的值存入结果数组ans
。 - 然后,算法的核心任务是计算出下一个
(i, j)
的位置。
- 算法通过一个
-
移动与边界处理逻辑:
- 尝试常规移动:首先,根据当前方向
f
计算出下一个“理想”位置(ni, nj)
。- 如果
f=1
(右上),则ni = i - 1
,nj = j + 1
。 - 如果
f=-1
(左下),则ni = i + 1
,nj = j - 1
。
- 如果
- 判断是否越界:接下来是最关键的部分——判断
(ni, nj)
是否超出了矩阵的边界。- 如果未越界:
ni
和nj
都在[0, m-1]
和[0, n-1]
的有效范围内,那么直接更新i = ni
,j = nj
,完成一步移动。 - 如果越界:这说明遍历的路径已经撞到了矩阵的边缘,此时需要改变方向并找到下一条对角线的起点。
- 撞到上或右边界 (右上移动时):
ni < 0
(上) 或nj == n
(右)。- 此时必须改变方向,
f = -f
。 - 确定“拐点”:如果撞到的是右边界 (
nj == n
),则下一个位置在当前行的下一行i++
。如果撞到的是上边界 (ni < 0
),则下一个位置在当前列的下一列j++
。
- 此时必须改变方向,
- 撞到下或左边界 (左下移动时):
ni == m
(下) 或nj < 0
(左)。- 同样改变方向,
f = -f
。 - 确定“拐点”:如果撞到的是下边界 (
ni == m
),则下一个位置在当前列的下一列j++
。如果撞到的是左边界 (nj < 0
),则下一个位置在当前行的下一行i++
。
- 同样改变方向,
- 撞到上或右边界 (右上移动时):
- 如果未越界:
- 尝试常规移动:首先,根据当前方向
通过这种“尝试移动 -> 检查边界 -> 若越界则拐弯并变向”的循环,算法精确地模拟了对角线遍历的完整路径。
完整代码
class Solution {/*** 对一个 m x n 的矩阵进行对角线遍历。* @param mat 输入的二维矩阵* @return 按对角线遍历顺序的一维数组*/public int[] findDiagonalOrder(int[][] mat) {// f: 方向标志。1 表示向右上移动,-1 表示向左下移动。int f = 1;int m = mat.length;int n = mat[0].length;// i, j: 当前在矩阵中的行和列索引int i = 0;int j = 0;// ans: 存储最终结果的一维数组int[] ans = new int[m * n];// 循环 m * n 次,确保遍历所有元素for (int k = 0; k < m * n; k++) {// 1. 将当前位置的元素存入结果数组ans[k] = mat[i][j];// 2. 计算下一个理论位置 (ni, nj)// 如果 f=1, (i-1, j+1); 如果 f=-1, (i+1, j-1)int ni = i - f;int nj = j + f;// 3. 边界检查与移动逻辑// A. 处理向上或向右越界的情况 (发生在 f=1, 即右上移动时)if (ni < 0 || nj == n) {// 撞到右边界 (nj == n),拐点在下一行if (nj == n) {i++;} else { // 撞到上边界 (ni < 0),拐点在下一列j++;}// 改变方向,准备向左下移动f = -f;// B. 处理向下或向左越界的情况 (发生在 f=-1, 即左下移动时)} else if (nj < 0 || ni == m) {// 撞到下边界 (ni == m),拐点在下一列if (ni == m) {j++;} else { // 撞到左边界 (nj < 0),拐点在下一行i++;}// 改变方向,准备向右上移动f = -f;// C. 正常情况:没有越界} else {// 直接移动到计算出的下一个位置i = ni;j = nj;}}return ans;}
}
时空复杂度
时间复杂度:O(M * N)
- 循环:算法的核心是一个
for
循环,该循环从k = 0
到m * n - 1
,总共执行M * N
次,其中M
是矩阵的行数,N
是列数。 - 循环内部操作:
- 在循环的每一次迭代中,执行的都是常数时间的操作:数组读写、算术运算、条件判断和变量赋值。
- 所有这些操作的时间复杂度都是 O(1)。
综合分析:
总的时间复杂度是循环次数乘以单次循环的开销,即 (M * N) * O(1)
= O(M * N)。算法需要访问矩阵中的每一个元素一次。
空间复杂度:O(1) (不考虑输出数组)
- 主要存储开销:
int[] ans = new int[m * n]
: 这个数组是用来存储输出结果的,其大小为M * N
。在算法复杂度分析的通常约定中,返回结果所需的空间不计入额外辅助空间。- 其他变量:
f
,m
,n
,i
,j
,k
,ni
,nj
都是基本类型的变量,只占用固定的、常数级别的空间。
综合分析:
如果我们不考虑输出数组 ans
的空间,那么算法本身只使用了常数个变量。因此,其额外辅助空间复杂度为 O(1)。如果必须将输出数组也计算在内,则空间复杂度为 O(M * N)。