本文已参与「新人创作礼」活动,一起开启掘金创作之路。
出处
描述
给定一个整数矩阵,找出最长递增路径的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。
示例
输入: 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;
}
代码消耗
正常做法,深度优先遍历
计算量爆炸