题目
给定一个 n 行 m 列矩阵 matrix ,矩阵内所有数均为非负整数。 你需要在矩阵中找到一条最长路径,使这条路径上的元素是递增的。并输出这条最长路径的长度。
这个路径必须满足以下条件:
数据范围:1≤n,m≤1000,0≤matrix[i][j]≤1000。
进阶:空间复杂度 O(nm),时间复杂度 O(nm)。
例如:当输入为[[1,2,3],[4,5,6],[7,8,9]]时,对应的输出为5,其中的一条最长递增路径如下图所示:
示例1
输入:[[1,2,3],[4,5,6],[7,8,9]]
返回值:5
说明:1->2->3->6->9即可。当然这种递增路径不是唯一的。
示例2
输入:[[1,2],[4,3]]
返回值:4
说明: 1->2->3->4
备注:矩阵的长和宽均不大于1000,矩阵内每个数不大于1000。
思路1:深度优先搜索(dfs)(推荐使用)
深度优先搜索一般用于树或者图的遍历,其他有分支的(如二维矩阵)也适用。
它的原理是从初始点开始,一直沿着同一个分支遍历,直到该分支结束,然后回溯到上一级继续沿着一个分支走到底,如此往复,直到所有的节点都有被访问到。
既然是查找最长的递增路径长度,那我们首先要找到这个路径的起点,起点不好直接找到,就从上到下从左到右遍历矩阵的每个元素。然后以每个元素都可以作为起点查找它能到达的最长递增路径。
如何查找以某个点为起点的最长递增路径呢?我们可以考虑深度优先搜索,因为我们查找递增路径的时候,每次选中路径一个点,然后找到与该点相邻的递增位置,相当于进入这个相邻的点,继续查找递增路径,这就是递归的子问题。因此递归过程如下:
具体做法:
代码1
import java.util.*;
public class Solution {
//记录4个方向
private int[][] dirs = new int[][] {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
private int n, m;
public int solve (int[][] matrix) {
//边界条件判断
if (matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int res = 0;
n = matrix.length;
m = matrix[0].length;
//j,j处的单元格拥有的最长递增路径
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
//更新最大值
res = Math.max(res, dfs(matrix, dp, i, j));
}
}
return res;
}
//深度优先搜索,返回最大单元格数
public int dfs(int[][] matrix, int[][] dp, int i, int j) {
if (dp[i][j] != 0) {
return dp[i][j];
}
dp[i][j]++;
for(int k = 0; k < 4; k++) {
int nexti = i + dirs[k][0];
int nextj = j + dirs[k][1];
//判断下一个要遍历的位置是否满足条件:在矩阵范围内且满足路径递增
if(nexti >= 0 && nexti < n && nextj >= 0 && nextj < m && matrix[nexti][nextj] > matrix[i][j]) {
dp[i][j] = Math.max(dp[i][j], dfs(matrix, dp, nexti, nextj) + 1);
}
}
return dp[i][j];
}
}
思路2:广度优先搜索(bfs)
广度优先搜索与深度优先搜索不同,它是将与某个节点直接相连的其它所有节点依次访问一次之后,再往更深处,进入与其他节点直接相连的节点。
bfs的时候我们常常会借助队列的先进先出,因为从某个节点出发,我们将与它直接相连的节点都加入队列,它们优先进入,则会优先弹出,在它们弹出的时候再将与它们直接相连的节点加入,由此就可以依次按层访问。
我们可以将矩阵看成是一个有向图,一个元素到另一个元素递增,代表有向图的箭头。这样我们可以根据有向图的出度入度找到最长的路径,且这个路径在矩阵中就是递增的。
具体做法:
代码
import java.util.*;
public class Solution {
//记录4个方向
private int[][] dirs = new int[][] {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
private int n, m;
public int solve (int[][] matrix) {
//边界条件判断
if (matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int res = 0;
n = matrix.length;
m = matrix[0].length;
//j,j处的单元格拥有的最长递增路径
int[][] outdegrees = new int[m + 1][n + 1];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
for (int k = 0; k < 4; k++) {
int nexti = i + dirs[k][0];
int nextj = j + dirs[k][1];
if (nexti >= 0 && nexti < n && nextj >= 0 && nextj < m &&
matrix[nexti][nextj] > matrix[i][j]) {
//符合条件,记录出度
outdegrees[i][j]++;
}
}
}
}
//辅助队列
Queue q1 = new LinkedList();
Queue q2 = new LinkedList();
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (outdegrees[i][j] == 0) {
//找到出度为0的入队
q1.offer(i);
q2.offer(j);
}
}
}
while (!q1.isEmpty()) {
res++;
int size = q1.size();
for (int x = 0; x < size; x++) {
int i = q1.poll();
int j = q2.poll();
//四个方向
for (int k = 0; k < 4; k++) {
int nexti = i + dirs[k][0];
int nextj = j + dirs[k][1];
//逆向搜索,所以下一步是小于
if (nexti >= 0 && nexti < n && nextj >= 0 && nextj < m &&
matrix[nexti][nextj] < matrix[i][j]) {
//符合条件,出度递减
outdegrees[nexti][nextj]--;
if (outdegrees[nexti][nextj] == 0) {
q1.offer(nexti);
q2.offer(nextj);
}
}
}
}
}
return res;
}
}