leetcode
leetcode 2951 ~ 3000
矩阵中的最长递增路径

矩阵中的最长递增路径

难度:

标签:

题目描述

English description is not available for the problem. Please switch to Chinese.

代码结果

运行时间: 116 ms, 内存: 17.0 MB


/*
 * 思路:
 * 1. 我们依旧定义一个方向数组dirs,表示四个可能的方向。
 * 2. 使用一个记忆化的二维数组dp,其中dp[i][j]表示以matrix[i][j]为起点的最长递增路径的长度。
 * 3. 对于每个单元格(i, j),使用深度优先搜索(DFS)来找到以它为起点的最长递增路径。
 * 4. 在DFS过程中,如果移动后的值大于当前值,递归计算移动后的单元格的最长递增路径长度,并更新dp[i][j]。
 * 5. 最后遍历dp数组,找到最大的值即为答案。
 */

import java.util.Arrays;
import java.util.stream.IntStream;

public class Solution {
    private static final int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
    private int m, n;

    public int longestIncreasingPath(int[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return 0;
        }
        m = matrix.length;
        n = matrix[0].length;
        int[][] dp = new int[m][n];
        return IntStream.range(0, m)
            .flatMap(i -> IntStream.range(0, n)
            .map(j -> dfs(matrix, i, j, dp)))
            .max()
            .orElse(0);
    }

    private int dfs(int[][] matrix, int i, int j, int[][] dp) {
        if (dp[i][j] != 0) {
            return dp[i][j];
        }
        int max = 1;
        for (int[] dir : dirs) {
            int x = i + dir[0], y = j + dir[1];
            if (x >= 0 && x < m && y >= 0 && y < n && matrix[x][y] > matrix[i][j]) {
                max = Math.max(max, 1 + dfs(matrix, x, y, dp));
            }
        }
        dp[i][j] = max;
        return max;
    }
}

解释

方法:

该题解采用了拓扑排序的思想,将矩阵中的每个单元格视为图中的一个节点,如果一个单元格的值小于它相邻的单元格,则在这两个单元格之间存在一条有向边。因此,该问题转化为了找出这个有向图中的最长路径。首先,我们计算每个单元格的出度(即有多少条边从该单元格指向其他单元格),并将出度为0的单元格(即没有任何边指向其他单元格的单元格)加入队列中。然后,我们开始进行拓扑排序,每次从队列中取出一个单元格,并将其相邻单元格的出度减1,如果某个相邻单元格的出度变为0,则将其加入队列中。每次从队列中取出单元格时,路径长度加1。重复这个过程,直到队列为空,此时的路径长度即为最长递增路径的长度。

时间复杂度:

O(mn)

空间复杂度:

O(mn)

代码细节讲解

🦆
在实现拓扑排序时,您是如何处理矩阵边界的?例如,如果一个单元格位于矩阵的边缘,您会如何确保不会检查超出矩阵边界的单元格?
在实现拓扑排序时,我通过条件检查来处理矩阵的边界问题。具体来说,每当需要检查一个单元格的相邻单元格时,我会首先验证这个相邻单元格是否存在,即它的索引是否在合法的范围内。例如,如果要检查一个单元格上方的单元格,则会确认`i - 1 >= 0`;检查左侧单元格时确认`j - 1 >= 0`;检查下方单元格时确认`i + 1 < m`;检查右侧单元格时确认`j + 1 < n`。这样的条件判断确保了不会访问到矩阵的外部,避免了数组越界的错误。
🦆
题解中提到了将出度为0的单元格加入队列,能否解释为什么选择出度为0的单元格开始而非入度为0的单元格?
题解中选择将出度为0的单元格加入队列是因为这些单元格没有任何后续单元格,即没有更大的值可以从这些单元格继续递增。这是递增路径的终点。从这些终点开始,进行逆向拓扑排序,可以确保每次处理时,所有被考虑到的单元格都是当前路径的最长可能延伸。这种方法反向追踪递增路径,与常规拓扑排序的方向相反,常规拓扑排序通常从入度为0的节点开始,即没有依赖的起点开始。在这个问题中,我们关注的是最长递增路径的终结,因此从出度为0的单元格开始更合适。
🦆
在拓扑排序的过程中,您是如何保证每次从队列中取出单元格时路径长度都正确增加的?特别是在多个单元格同时出队时。
在拓扑排序的实现中,我通过在每个循环中处理整个当前队列来适当增加路径长度。具体而言,每当队列中的单元格被完全处理(即该层的所有单元格都被取出)后,路径长度才会增加1。这是因为路径长度的增加代表了递增路径中的一个层级的完成。在处理过程中,每次循环处理当前队列中的所有单元格,然后再将新的出度变为0的单元格添加到队列中。这保证了在每个单元格被处理时,它所依赖的所有单元格已经被处理过,符合拓扑排序中层级递增的逻辑。

相关问题