算法-动态规划-最小路径和

算法-动态规划-最小路径和

1 题目概述

1.1 题目出处

https://leetcode-cn.com/problems/minimum-path-sum/

1.2 题目描述

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

2 BFS

2.1 思路

从左上角开始,进行BFS,每次拿出队首元素,然后分别计算该元素的路径和加上右侧和下侧元素的数值之和,看是否分别小于当前右和下侧元素的路径和,如果小于就更新,并且在这些元素未访问过时推入队列末尾进行BFS。

2.2 代码

class Solution {
    public int minPathSum(int[][] grid) {
        int result = 0;
        if(grid == null || grid.length == 0){
            return result;
        }

        int m = grid.length - 1;
        int n = grid[0].length - 1;

        // 用来存放某节点的最小路径和
        int[][] record = new int[m + 1][n + 1];  
        for(int i = 0; i < record.length; i++){
            for(int j = 0; j < record[0].length; j++){
                record[i][j] = -1;
            }
        }
        
       
        // 思路 从左上开始,分别计算右侧和下侧节点的最小路径和,并推入队列等待计算

        LinkedList<int[]> queue = new LinkedList<>();
        record[0][0] = grid[0][0];
        int[] first = {0, 0, record[0][0]};
        queue.add(first);
        while(!queue.isEmpty()){
            int[] ele = queue.poll();
            if(ele[1] < n){
                if(record[ele[0]][ele[1] + 1] == -1){
                    record[ele[0]][ele[1] + 1] = record[ele[0]][ele[1]] + grid[ele[0]][ele[1] + 1];
                    int[] right = {ele[0], ele[1] + 1, record[ele[0]][ele[1] + 1]};
                    queue.add(right);
                }else{
                    record[ele[0]][ele[1] + 1] = Math.min(record[ele[0]][ele[1] + 1], record[ele[0]][ele[1]] + grid[ele[0]][ele[1] + 1]);
                }
            }

            if(ele[0] < m){
                if(record[ele[0] + 1][ele[1]] == -1){
                    record[ele[0] + 1][ele[1]] = record[ele[0]][ele[1]] + grid[ele[0] + 1][ele[1]];
                    int[] below = {ele[0] + 1, ele[1], record[ele[0] + 1][ele[1]]};
                    queue.add(below);
                }else{
                    record[ele[0] + 1][ele[1]] = Math.min(record[ele[0] + 1][ele[1]], record[ele[0]][ele[1]] + grid[ele[0] + 1][ele[1]]);
                }
            }
        }
        return record[m][n];
    }
}

2.3 时间复杂度

在这里插入图片描述
O(m*n)

2.4 空间复杂度

O(m*n)

3 DFS

3.1 思路

既然写了BFS,那为了练习我们可以来个DFS。

但有个问题,如果采用DFS,先遍历右侧,那可能导致有些节点因为已经被访问过而没有更新更小的值!

也就是说,我们不能像之前那样不管已经访问过的节点,需要再次访问!

3.2 代码

class Solution {
    private int m = 0;

    private int n = 0;
    public int minPathSum(int[][] grid) {
        int result = 0;
        if(grid == null || grid.length == 0){
            return result;
        }

        m = grid.length - 1;
        n = grid[0].length - 1;

        // 用来存放某节点的最小路径和
        int[][] record = new int[m + 1][n + 1];  
        for(int i = 0; i < record.length; i++){
            for(int j = 0; j < record[0].length; j++){
                record[i][j] = -1;
            }
        }

        record[0][0] = grid[0][0];
        dfs(grid, record, 0, 0);
        return record[m][n];
    }

    private void dfs(int[][] grid, int[][] record, int i, int j){
        if(j < n){
            if(record[i][j + 1] == -1){
                record[i][j + 1] = record[i][j] + grid[i][j + 1];
                
            }else{
                record[i][j + 1] = Math.min(record[i][j + 1], record[i][j] + grid[i][j + 1]);
            }
            dfs(grid, record, i, j+1);
        }

        if(i < m){
            if(record[i + 1][j] == -1){
                record[i + 1][j] = record[i][j] + grid[i + 1][j];
                
            }else{
                record[i + 1][j] = Math.min(record[i + 1][j], record[i][j] + grid[i + 1][j]);
            }
            dfs(grid, record, i+1, j);
        }
    }
}

3.3 时间复杂度

在这里插入图片描述
果然,超出了时间限制!

4 DFS-优化

4.1 思路

从右下往左上进行DFS:

record[i][j] = grid[i][j] + Math.min(dfs(grid, record, i - 1, j), dfs(grid, record, i, j - 1));

还是采用record进行记录,但注意这里因为已经是最终的最小路径和,所以重复访问时可以直接返回该值,不需要再进行计算!

4.2 代码

class Solution {
    private int m = 0;

    private int n = 0;
    public int minPathSum(int[][] grid) {
        int result = 0;
        if(grid == null || grid.length == 0){
            return result;
        }

        m = grid.length - 1;
        n = grid[0].length - 1;
        // 1.缓存最小路径和
        // 2.标识是否访问过
        Integer[][] record = new Integer[m + 1][n + 1];

        // 思路 从右下开始往左和上推
        return dfs(grid, record, m, n);
    }

    private int dfs(int[][] grid, Integer[][] record, int i, int j){
        if(i < 0 || j < 0){
            // 越界
            return Integer.MAX_VALUE;
        }
        
        if(i == 0 && j == 0){
            return grid[0][0];
        }

        if(record[i][j] != null){
            return record[i][j];
        }
        record[i][j] = grid[i][j] + Math.min(dfs(grid, record, i - 1, j), dfs(grid, record, i, j - 1));
        return record[i][j];
    }
}

4.3 时间复杂度

在这里插入图片描述
O(m*n)

4.4 空间复杂度

O(m*n)

5 动态规划1

5.1 思路

前面BFS看起来不错,但其实好像没有必要用队列,而且速度也很慢。我们改成动态规划试试。

因为我们遍历采用的是从左到右,从上到下,所以是可以保证每个节点都是最小路径和。

动态规划转移方程如下:

dp[i][j] = Math.min(dp[i - 1][j], dp[i][j-1]) + grid[i-1][j-1];

其中dp[i][j]表示该节点的最小路径和。

5.2 代码

class Solution {
    public int minPathSum(int[][] grid) {
        int result = 0;
        if(grid == null || grid.length == 0){
            return result;
        }

        int m = grid.length;
        int n = grid[0].length;

        // 用来存放某节点的最小路径和
        int[][] dp = new int[m + 1][n + 1];  
        // 在外围虚拟一圈,除了grid[0][0]的左和上为0外,其他值都为Integer.MAX_VALUE;
        // 这样做的目的是为了动态规划遍历中不用管边界值
        int[] zeroRow = dp[0];
        for(int i = 2; i < zeroRow.length; i++){
            zeroRow[i] = Integer.MAX_VALUE;
        }
        for(int i = 2; i < m + 1; i++){
            dp[i][0] = Integer.MAX_VALUE;
        }

        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                dp[i][j] = Math.min(dp[i - 1][j], dp[i][j-1]) + grid[i-1][j-1];
            }
        }  
        return dp[m][n];
    }
}

5.3 时间复杂度

在这里插入图片描述
O(m*n)

这次速度快很多了!

5.4 空间复杂度

O(m*n)

6 动态规划2

6.1 思路

前面动态规划1看起来很妙,但还用了额外空间。

仔细观察,其实按上面方法遍历,每个元素的初始值只用了一次,那么可以复用原始数组grid,遍历过程中将计算的最小路径和放入里面。也就是说grid[i][j]最终表示该节点的最小路径和。

这样,最后直接返回grid[m-1][n-1]即可

6.2 代码

class Solution {
    public int minPathSum(int[][] grid) {
        int result = 0;
        if(grid == null || grid.length == 0){
            return result;
        }

        int m = grid.length;
        int n = grid[0].length;

        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(i + j == 0){
                    continue;
                }else if (i == 0){
                    grid[i][j] = grid[i][j-1] + grid[i][j];
                }else if (j == 0){
                    grid[i][j] = grid[i - 1][j] + grid[i][j];
                }else{
                    grid[i][j] = Math.min(grid[i - 1][j], grid[i][j-1]) + grid[i][j];
                }
            }
        }  
        return grid[m-1][n-1];
    }
}

6.3 时间复杂度

O(m*n)

6.4 空间复杂度

在这里插入图片描述
O(1)
因为复用原数组grid,没有额外空间。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章