【动态规划】—— 路径问题

文章目录:

1. 动态规划基本步骤

2. 路径问题模型例题

2.1:不同路径

2.1.1:算法原理

2.1.2:代码实现

2.2:不同路径 2

2.2.1:算法原理

2.2.2:代码实现

2.3:礼物的最大价值

2.3.1:算法原理

2.3.2:代码实现

2.4:下降路径最小和

2.4.1:算法原理

2.4.2:代码实现

2.5:最小路径和

2.5.1:算法原理

2.5.2:代码实现

2.6:地下城游戏

2.6.1:算法原理

2.6.2:代码实现


今天我们开始动态规划例题的第二个模型——路径问题

叠甲叠甲叠甲:今天的题目全部取自力扣!!!

在开始之前,我们重新回忆一下动态规划的基本步骤

1. 动态规划基本步骤

        1. 状态表示:

                a. 创建一个dp表(通常是一个数组)

                b. 填满dp表,dp表的每一个值就是一个状态

                c. 定义状态表示的方法:

                        i. 题目要求

                        ii. 经验 + 题目要求:

                                1)以某一个位置作为起始

                                2)以某一个位置作为结束

                        iii. 分析问题的过程中,发现了重复子问题

        2. 状态转移方程:根据状态表示得出状态转移方程

                用之前或之后的状态推导出当前状态的值

                通常:根据最近的一步来划分问题

        3. 初始化:保证填表的时候不发生越界

                只需要初始化可能发生越界的状态

        4. 填表顺序:根据状态转移方程和边界条件,确定子问题的求解顺序

                通常是从下到上、从左到右(或称为从小到大)地计算状态值。

        5. 返回值

2. 路径问题模型例题

2.1:不同路径

2.1.1:算法原理

        1. 状态表示:

                a. 创建一个dp表(本题是一个二维数组)

                        i. 经验 + 题目要求:

                                1)以某一个位置作为结束

                                         dp[i][j]表示走到(i,j)位置的时候一共有多少方式

        2. 状态转移方程:

               dp[i][j]:

                        从上面来:(i-1,j)——> (i,j) :dp[i-1][j] 

                        从左面来:(i,j-1)——> (i,j) :dp[i][j-1] 

                状态转移方程:dp[i][j] = dp[i-1][j] + dp[i][j-1] 

        3. 初始化:

                本题:填写一个状态时,会用到上面和左面的节点,最上一行和最左一列会发生越界

                使用虚拟节点法:

                

                       虚拟节点的注意事项:

                        a. 虚拟节点的值要保证后续填表的结果是正确的

                                本题需要保证dp[1][1]=1,而dp[1][1] = dp[0][1] + dp[1][0]

                                需要将dp[0[1] or dp[1][0]初始化为1

                                

                        b. 注意下标的映射:

                        上文的dp[1][1]其实就是原始数组的(0,0)位置

        4. 填表顺序:

                从上到下、从左到右

        5. 返回值:dp[m][n]

2.1.2:代码实现

    //不同路径问题
    public int uniquePaths(int m, int n) {
        // 创建dp表
        //初始化
        //填表
        //返回值

        int[][] dp = new int[m+1][n+1];
        dp[0][1]=1;
        for(int i=1;i<=m;i++){
            for(int j =1;j<=n;j++){
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }

        return dp[m][n];

        //时间/空间复杂度 O(m*n)
    }

2.2:不同路径 2

2.2.1:算法原理

        1. 状态表示:

                a. 创建一个dp表(本题是一个二维数组)

                        i. 经验 + 题目要求:

                                1)以某一个位置作为结束

                                         dp[i][j]表示走到(i,j)位置的时候一共有多少方式

        2. 状态转移方程:

               dp[i][j]:

                        有障碍:0

                        无障碍:

                                从上面来:(i-1,j)——> (i,j) :dp[i-1][j] 

                                从左面来:(i,j-1)——> (i,j) :dp[i][j-1] 

                                dp[i][j] = dp[i-1][j] + dp[i][j-1] 

        3. 初始化:

                使用虚拟节点法:

                                本题同上题,需要保证dp[1][1]=1,而dp[1][1] = dp[0][1] + dp[1][0]

                                所以需要将dp[0[1] or dp[1][0]初始化为1

                                

        4. 填表顺序:

                从上到下、从左到右

        5. 返回值:dp[m][n]

2.2.2:代码实现

    //不同路径 2
    public int uniquePathsWithObstacles(int[][] ob) {
        // 创建dp表
        //初始化
        //填表
        //返回值

        int m = ob.length;
        int n = ob[0].length;
        int[][] dp=new int[m+1][n+1];
        dp[1][0]=1;
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(ob[i-1][j-1]==0){//注意下标的映射关系
                    dp[i][j]=dp[i-1][j]+dp[i][j-1];
                }
            }
        }
        return dp[m][n];
    }

2.3:礼物的最大价值

2.3.1:算法原理

        1. 状态表示:

                a. 创建一个dp表(本题是一个二维数组)

                        i. 经验 + 题目要求:

                                1)以某一个位置作为结束

                                         dp[i][j]表示走到(i,j)位置时的最大价值

        2. 状态转移方程:

               dp[i][j]:

                        从上面来:(i-1,j)——> (i,j) :dp[i-1][j] + (i,j)位置的价值

                        从左面来:(i,j-1)——> (i,j) :dp[i][j-1] + (i,j)位置的价值

                dp[i][j] = max(dp[i-1][j] , dp[i][j-1]) + (i,j)位置的价值

        3. 初始化:

                使用虚拟节点法:

                

                因为每走到一个位置都会加上当前位置的价值

                所以辅助节点全部初始化为0即可(就是默认状态,不需要做处理)

        4. 填表顺序:

                从上到下、从左到右

        5. 返回值:dp[m][n]

2.3.2:代码实现

    //礼物的最大价值
    public int jewelleryValue(int[][] frame) {
        // 创建dp表
        //初始化
        //填表
        //返回值

        int m = frame.length;
        int n = frame[0].length;
        int[][] dp=new int[m+1][n+1];
        dp[1][0]=1;
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1])+frame[i-1][j-1];
            }
        }
        return dp[m][n];
    }

2.4:下降路径最小和

2.4.1:算法原理

        1. 状态表示:

                a. 创建一个dp表(本题是一个二维数组)

                        i. 经验 + 题目要求:

                                1)以某一个位置作为结束

                                         dp[i][j]表示走到(i,j)位置时的最小下降路径

        2. 状态转移方程:

               dp[i][j]:

                        从左上面来:(i-1,j-1)——> (i,j) :dp[i-1][j-1] + (i,j)位置的值

                        从正上面来:(i-1,j)——> (i,j) :dp[i-1][j] + (i,j)位置的值

                        从右上面来:(i-1,j+1)——> (i,j) :dp[i-1][j+1] + (i,j)位置的值

                dp[i][j] = min(dp[i-1][j-1] , dp[i-1][j], dp[i-1][j+1]) + (i,j)位置的值

        3. 初始化:

                填表时会用到当前节点的左上、正上、右上的状态

                所以:最上一行、最左一列、最右一列会发生越界

                使用虚拟节点法:

                

                只需要将所有默认节点初始化为+∞,再将最上一行初始化为0

                

                第一行的值要和matrix数组一致,不能受虚拟节点影响,所以最上一行初始化为0

                因为要取最小值,不能让左列和右列的值产生影响,所以将左列和右列初始化为+∞

        4. 填表顺序:

                从上到下

        5. 返回值:最后一行的最小值

2.4.2:代码实现

    //下降路径最小和
    public int minFallingPathSum(int[][] matrix) {
        // 创建dp表
        //初始化
        //填表
        //返回值

        int n = matrix.length;
        int[][] dp = new int[n + 1][n + 2];

        for (int i = 1; i <= n; i++) {
            dp[i][0] = dp[i][n + 1] = Integer.MAX_VALUE;
        }
        //填表
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                dp[i][j] = Math.min(dp[i - 1][j], Math.min(dp[i - 1][j - 1], dp[i - 1][j + 1])) + matrix[i-1][j-1];
            }
        }
        //找出最后一行的最小值
        int ret = Integer.MAX_VALUE;
        //求一系列数的最小值时,要把ret设置得尽可能大,使其不影响最终结果
        //如果使ret=0;而其他数都比0大,则影响了最终结果
        for (int j = 1; j <= n; j++) {
            ret = Math.min(ret, dp[n][j]);
        }

        return ret;
        
        
        //时间/空间复杂度:O(n^2)
    }

2.5:最小路径和

2.5.1:算法原理

        1. 状态表示:

                a. 创建一个dp表(本题是一个二维数组)

                        i. 经验 + 题目要求:

                                1)以某一个位置作为结束

                                         dp[i][j]表示走到(i,j)位置时的最小路径和

        2. 状态转移方程:

               dp[i][j]:

                        从上面来:(i-1,j)——> (i,j) :dp[i-1][j] + (i,j)位置的值

                        从左面来:(i,j-1)——> (i,j) :dp[i][j-1] + (i,j)位置的值

                dp[i][j] = min(dp[i-1][j] , dp[i][j-1]) + (i,j)位置的值

        3. 初始化:

                最上一行、最左一列会发生越界

                使用虚拟节点法:

                

                dp[0][1]和dp[1][0]初始化为0,其他虚拟节点初始化为+∞

        4. 填表顺序:

                从上到下、从左到右

        5. 返回值:dp[m][n]

2.5.2:代码实现

    //最小路径和
    public int minPathSum(int[][] grid) {
        // 创建dp表
        //初始化
        //填表
        //返回值

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

        return dp[m][n];
    }

2.6:地下城游戏

2.6.1:算法原理

        1. 状态表示:

                a. 创建一个dp表(本题是一个二维数组)

                        i. 经验 + 题目要求:

                                1)以某一个位置作为结束

                                         dp[i][j]表示走到(i,j)位置时所需的最小初始健康值

                                        当前状态受到之后状态的影响,无法确定当前状态

                                        (涉及无后效性问题)所以该方案行不通

                                2)以某一个位置作为开始

                                         dp[i][j]表示从(i,j)位置出发,到达终点所需的最低初始健康值

        2. 状态转移方程:

               dp[i][j]:

                        往右走:(i,j)——> (i,j+1) :dp[i][j+1] - dp[i][j] 

                        往下走:(i,j)——> (i+1,j) :dp[i+1][j] - dp[i][j] 

                dp[i][j] = min(dp[i][j+1] - dp[i][j] , dp[i+1][j] - dp[i][j])

                dp[i][j] = max( dp[i][j],1) 保证血量不会低于1

        3. 初始化:

                最下一行、最右一列会发生越界

                使用虚拟节点法:

                

                dp[m][n-1]和dp[m-1][n]初始化为1,其他虚拟节点初始化为+∞

        4. 填表顺序:

                从下到上、从右到左

        5. 返回值:dp[0][0]

2.6.2:代码实现

    //地下城游戏
    public int calculateMinimumHP(int[][] dungeon) {
        // 创建dp表
        //初始化
        //填表
        //返回值

        int m = dungeon.length;
        int n = dungeon[0].length;
        int[][] dp = new int[m+1][n+1];

        //初始化
        for(int i=0;i<=m;i++){
            dp[i][n]=Integer.MAX_VALUE;
        }
        for(int j=0;j<=n;j++){
            dp[m][j]=Integer.MAX_VALUE;
        }
        dp[m][n-1]=dp[m-1][n]=1;
        //填表
        for (int i = m-1; i >= 0; i--) {
            for (int j = n-1; j >= 0; j--) {
                dp[i][j] =  Math.min(dp[i][j+1], dp[i+1][j]) - dungeon[i][j];
                //保证最低健康值不会小于1
                dp[i][j] = Math.max(1,dp[i][j]);
            }
        }

        return dp[0][0];
    }

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值