题目描述(leetcode329题)
基于深度优先搜索
我们可以把矩阵每个单元格看作一个点,而若一个点的值小于相邻点的值,那么就看作这两个点之间有一条有向边,由小的点指向大的点。这样,求最长递增路径的问题,就变成了求有向无环图最长路径的问题。我们可以用深度优先搜索来解决。
那么问题又来了,如果直接使用深度优先搜索,那么将会大量重复计算一些节点的路径,因此我们采用记忆化的方法,用一个矩阵记录下已经算好的节点,若这个矩阵的节点值为0,则是没有计算过的,需要计算,否则,直接用它的值就可以了。
/**
* @param {number[][]} matrix
* @return {number}
*/
var longestIncreasingPath = function(matrix) {
var m = matrix.length; var n = matrix[0].length;
var path = [];
var res = 0;
var dfs = (i, j) => {
if(path[i][j] != 0) return path[i][j];
let distance = 0;
//可以往上走
if(i>0 && matrix[i][j] < matrix[i-1][j]){
distance = Math.max(dfs(i-1,j),distance);
}
//可以往下走
if(i<m-1 && matrix[i][j] < matrix[i+1][j]){
distance = Math.max(dfs(i+1,j),distance);
}
//可以往左走
if(j>0 && matrix[i][j] < matrix[i][j-1]){
distance = Math.max(dfs(i,j-1),distance);
}
//可以往右走
if(j<n-1 && matrix[i][j] < matrix[i][j+1]){
distance = Math.max(dfs(i,j+1),distance);
}
path[i][j] = 1 + distance;
return path[i][j];
}
for(let i=0;i<m;i++){
path[i] = [];
for(let j=0;j<n;j++){
path[i][j] = 0;
}
}
for(let i=0;i<m;i++){
for(let j=0;j<n;j++){
res = Math.max(dfs(i,j), res);
}
}
return res;
};
时间复杂度:O(mn) (深度优先搜索的时间复杂度为O(V+E),这里V=mn,E=4mn)
空间复杂度:O(mn)
基于拓扑排序(动态规划)
其实我一开始也有用动态规划解决问题的想法,用一个矩阵记录以该节点为起点最多能沿着递增路径走多远。
递推公式: dp[i][j] = 1 + max(dp[x][y]) (x,y为i,j相邻的比i,j大的点)
然后通过不断更新dp矩阵,直到矩阵不变,矩阵中最大的值便是最长路径的长度。但是最坏情况下,可能要计算m+n-1次。从时间复杂度上说,是绝对不可取的。而官方题解,则给出了一个更加优雅的解法。
首先,计算所有节点的出度(从该节点能走到相邻的节点,则出度+1)。出度为0的,则一定是路径的终点,我们把它加进队列里面去。利用基于广度优先搜索的思路,不断更新出度矩阵:本轮中,对于队列里的每一个顶点v,如果其相邻的点u有一条到v的边,那么u的出度-1。这时,如果u的出度变为0,也就意味着u除了能通往v,不能再通向其他节点了。把u也加进队列里,等待下一轮的计算。在这里,我们计算了多少轮,其实也就代表着最长路径的长度。
var longestIncreasingPath = function(matrix) {
var direction = [[0,-1],[0,1],[-1,0],[1,0]];
var m = matrix.length; var n = matrix[0].length;
var outDegree = [];
var queue = [];
for(let i = 0;i<m;i++){
outDegree[i] = [];
for(let j=0;j<n;j++){
outDegree[i][j] = 0;
for(let dir of direction){
let row = i + dir[0];
let col = j + dir[1];
if(row >= 0 && row < m && col >=0 && col < n && matrix[i][j] < matrix[row][col]){
outDegree[i][j]++
}
}
if(outDegree[i][j] == 0){
queue.push([i,j]);
}
}
}
var res = 0;
while(queue.length != 0){
res++;
let len = queue.length;
for(let l=0;l<len;l++){
let cur = queue.shift();
let i = cur[0]; let j = cur[1];
for(let dir of direction){
let row = i + dir[0];
let col = j + dir[1];
if(row >= 0 && row < m && col >=0 && col < n && matrix[row][col] < matrix[i][j]){
outDegree[row][col]--;
if(outDegree[row][col] == 0){
queue.push([row,col]);
}
}
}
}
}
return res;
};
在这个解法中,用direction数组来计算相邻点坐标,再验证其有效性,相比第一种解法的四个if判断,更加的高效。
时间复杂度:O(mn)
空间复杂度:O(mn)