329. 矩阵中的最长递增路径
思路:给定一个整数矩阵,找出最长递增路径的长度。对于每个单元格,你可上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。
我觉得这题怎么说都算不上是困难题,这和最大路径和基本上思路是一样的,直接对矩阵中所有的点DFS,遍历其四个方向,选择满足条件且路径最长的即可。
但是DFS虽然可解释性强但是时间复杂度还是很高的,所以我们在加一个memo数组存放已经遍历过的结果(记忆化回溯),这些都是很老的套路了,代码如下:
private int row;
private int col;
private int[][] position = new int[][]{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
private int[][] memo;
public int longestIncreasingPath(int[][] matrix) {
if (null == matrix || matrix.length < 1) return 0;
row = matrix.length;
col = matrix[0].length;
memo = new int[row][col];
int res = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
res = Math.max(res, dfs(matrix, i, j));
}
}
return res;
}
private int dfs(int[][] matrix, int i, int j) {
// 查缓存,如果缓存中有直接return
if (memo[i][j] != 0) {
return memo[i][j];
}
int max = 0;
for (int[] pos : position) {
int x = i + pos[0];
int y = j + pos[1];
if (x >= 0 && x < row && y >= 0 && y < col && matrix[x][y] > matrix[i][j]) {
max = Math.max(max, dfs(matrix, x, y)) + 1;
}
}
// 缓存结果
memo[i][j] = max;
return max;
}
注意上面这个实际上是个错误答案,因为max = Math.max(max, dfs(matrix, x, y)) + 1;不可以在if中加一,这样会导致只有进入判断中的时候才有可能加一,所以这是错的!!!应该放到判断外:
private int row;
private int col;
private int[][] position = new int[][]{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
private int[][] memo;
public int longestIncreasingPath(int[][] matrix) {
if (null == matrix || matrix.length < 1) return 0;
row = matrix.length;
col = matrix[0].length;
memo = new int[row][col];
int res = 1;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
res = Math.max(res, dfs(matrix, i, j));
}
}
return res;
}
private int dfs(int[][] matrix, int i, int j) {
// 查缓存,如果缓存中有直接return
if (memo[i][j] != 0) {
return memo[i][j];
}
int max = 0;
for (int[] pos : position) {
int x = i + pos[0];
int y = j + pos[1];
if (x >= 0 && x < row && y >= 0 && y < col && matrix[x][y] > matrix[i][j]) {
max = Math.max(max, dfs(matrix, x, y));
}
}
// 缓存结果
memo[i][j] = 1 + max;
return memo[i][j];
}
也可以这样:
private int dfs(int[][] matrix, int i, int j) {
// 查缓存,如果缓存中有直接return
if (memo[i][j] != 0) {
return memo[i][j];
}
int max = 1;//只能为1,防止未进入if中的情况
for (int[] pos : position) {
int x = i + pos[0];
int y = j + pos[1];
if (x >= 0 && x < row && y >= 0 && y < col && matrix[x][y] > matrix[i][j]) {
max = Math.max(max, dfs(matrix, x, y) + 1);
}
}
// 缓存结果
memo[i][j] = max;
return memo[i][j];
}
之前说过,一般能用DFS的方法往往是可以使用动态规划的,因为这两种方法都是存在内在的递推关系的,这一题当然也可以,我们定义dp[i][j]表示以matrix[i][j]结尾的最长递增长度,则有若matrix[i][j]四个方向有任意小于它,则可以更新dp[i][j] = max(dp[i][j], 1 + dp[r][c])。
但是直接这样更新真的对吗?当然是错的!因为我们无法保证matrix[r][c]这个点是否被到达过(是否更新过),如果没有的话我们直接使用dp[i][j] = max(dp[i][j], 1 + dp[r][c]更新是毫无意义的!!!
因此我们需要先根据matrix所有值排序,按照排序后的顺序更新,即可保证先把matrix中值小的位置的dp先算出来,再把值大的位置的dp算出来,倒数第二行的代码就有意义了。
PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>(){
public int compare(int[] o1,int[] o2){
return matrix[o1[0]][o1[1]] - matrix[o2[0]][o2[1]];
}
});
for(int i=0;i<matrix.length;i++){
for(int j=0;j<matrix[0].length;j++){
queue.add(new int[]{i,j});
}
}
另外,还需要对数组初始化:
int[][] dp = new int[matrix.length][matrix[0].length];
for(int i=0;i<matrix.length;i++) Arrays.fill(dp[i],1);
完整代码如下:
public int longestIncreasingPath(int[][] matrix) {
if(matrix == null || matrix.length < 1) return 0;
int[][] position = new int[][]{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
int[][] dp = new int[matrix.length][matrix[0].length];
for(int i=0;i<matrix.length;i++) Arrays.fill(dp[i],1);
PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>(){
public int compare(int[] o1,int[] o2){
return matrix[o1[0]][o1[1]] - matrix[o2[0]][o2[1]];
}
});
for(int i=0;i<matrix.length;i++){
for(int j=0;j<matrix[0].length;j++){
queue.add(new int[]{i,j});
}
}
int max = 0;
while(!queue.isEmpty()){
int[] node = queue.poll();
int i = node[0];
int j = node[1];
for (int[] pos : position) {
int x = i + pos[0];
int y = j + pos[1];
if (x >= 0 && x < matrix.length && y >= 0 && y < matrix[0].length && matrix[x][y] < matrix[i][j]) {
dp[i][j] = Math.max(dp[i][j],dp[x][y]+1);
}
}
max = Math.max(max,dp[i][j]);
}
return max;
}