矩阵中的最长递增路径

331 阅读1分钟

题目描述(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)