【算法之路】Leecode No.329 矩阵中的最长递增路径【困难】

266 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

出处

矩阵中的最长递增路径

描述

给定一个整数矩阵,找出最长递增路径的长度。

对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。

示例

输入: nums = [ [9,9,4], [6,6,8], [2,1,1]] 输出: 4 解释: 最长递增路径为 [1, 2, 6, 9]。

输入: nums = [ [3,4,5], [3,2,6], [2,2,1]] 输出: 4 解释: 最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。

分析

看到这道题我们应该就知道大致的方向,通俗点来讲就是每个点依次查找,然后比较每一个节点的结果,输出最大的即可。一个矩阵,我们再考虑每一个点的时候都面临四个方向(上下左右,四种选择)分别计算四个方向的点的最大路径,一直这样找下去,直到超出边界或者计算结束。

问题的难点就在于对于不熟悉深度优先遍历算法的写不出来,或者说不知道该从何写起,以及其中的好多细节和临界条件想不明白,不知道是多少。我也是这样,所以这里写了一遍,把思路记录下来。整体分成两步

  • 步骤1:遍历每一个节点,找出最大的路径。(这里我们假定所有的路径数据都已经计算完成,所以直接遍历找到最大值就可以了)
  • 步骤2:计算某一个节点的最大路径,采用递归算法,计算某一个节点时,分别计算四个相邻(上下左右节点)的路径,找出最大的路径然后+1即可。

试着自己写一下吧,在脑海里记住这两个大致的步骤。

算法细节

如何找到当前节点的相邻节点

我们知道矩阵实际上就相当于是一个二位数组,那么当前节点(i, j)就表示为matrix[i][j],

  • 上边的节点:纵坐标不变,横坐标减一,即matrix[i - 1][j]
  • 右边的节点:横坐标不变,纵坐标加一,即matrix[i][j + 1]
  • 下边的节点:纵坐标不变,横坐标加一,即matrix[i + 1][j]
  • 左边的节点:横坐标不变,纵坐标减一,即mmatrix[i][j - 1]

如何确定每一个节点的最大路径

当前节点的最大路径一定是它某一个相邻节点的最大路径加一,那么我们只需要找到它所有相邻节点的最大路径就可以了

算法优化

缓存计算工作量

我们可以很容易就意识到如果按照上述方法进行实现,会存在大量的重复计算工作,比如,我们的matrix[i][j]下节点和matrix[i + 1][j + 1]的左节点在计算matrix[i][j]和matrix[i + 1][j + 1]的节点最大路径时都会计算到。

这样的算法肯定不是一个良好的算法,那么我们不难想到可以通过缓存来减少计算量,创建一个同宽同高的二维数组,用于记录每一个节点的最大路径。

更方便的获取某一节点的相邻节点

基于上述获取某一节点的相邻节点方法,我们可以创建一个二位数组,每一行表示相邻节点与当前节点的偏移量,第一列表示横坐标偏移量,第二列表示纵坐标偏移量。

那么当前的相邻节点的偏移量二维数组就是 {{0,1}, {1,0}, {0,-1}, {-1,0}}, 以第一个为例,matrix[2][3] (第三行,第四列的元素) 他的右节点就是 {0, 1}偏移量,即 2 + 0 = 2, 3 + 1 = 4,即他的右节点为matrix[2][4] (第三行,第五列的元素)

题解

正常做法,深度优先遍历

public int longestIncreasingPath(int[][] matrix) {
    int result =  0;
    // 步骤1
    for (int i = 0; i < matrix.length; i++) {
        for (int j = 0; j < matrix[i].length;j++) {
            result = Math.max(result, dfs(matrix, i, j));
        }
    }
    return result;
}

// 步骤2
private int dfs(int[][] matrix, int row, int column) {
    int max = 1;
    int[][] dirs = {{0,1}, {1,0}, {0,-1}, {-1,0}};
    for (int i = 0; i < dirs.length;i++) {
        int newRow = row + dirs[i][0];
        int newColumn = column + dirs[i][1];
        if (newRow >= 0 && newColumn >= 0 && newRow < matrix.length && newColumn < matrix[0].length && matrix[newRow][newColumn] < matrix[row][column]) {
            max = Math.max(max, dfs(matrix, newRow, newColumn) + 1);
        }
    }
    return max;
}

超时了!! 悲剧。

将所有计算过的矩阵中的点缓存起来

public int longestIncreasingPath(int[][] matrix) {
    int result =  0;
    if (matrix.length <= 0) {
        return result;
    }
    int[][] cookie = new int[matrix.length][matrix[0].length];
    for (int i = 0; i < matrix.length; i++) {
        for (int j = 0; j < matrix[i].length;j++) {
            result = Math.max(result, dfs(matrix, i, j, cookie));
        }
    }
    return result;
}

private int dfs(int[][] matrix, int row, int column, int[][] cookie) {
     if (cookie[row][column] != 0) {
         return cookie[row][column];
     }
     int max = 1;
     int[][] dirs = {{0,1}, {1,0}, {0,-1}, {-1,0}};
     for (int i = 0; i < dirs.length;i++) {
         int newRow = row + dirs[i][0];
         int newColumn = column + dirs[i][1];
         if (newRow >= 0 && newColumn >= 0 && newRow < matrix.length && newColumn < matrix[0].length && matrix[newRow][newColumn] < matrix[row][column]) {
             max = Math.max(max, dfs(matrix, newRow, newColumn, cookie) + 1);
         }
     }
     cookie[row][column] = max;
     return max;
 }

代码消耗

正常做法,深度优先遍历

计算量爆炸 在这里插入图片描述

将所有计算过的矩阵中的点缓存起来

在这里插入图片描述