• Day38——Dp专题


    DP专题

    动态规划五部曲:
    • 确定dp数组以及下标的含义

    • 确定递推公式

    • dp数组如何初始化

    • 确定遍历顺序

    • 举例推导dp数组

    1.斐波那契数

    题目链接:509. 斐波那契数 - 力扣(LeetCode)

    思路:做dp类题目,根据dp五部曲来的思路来解决,dp五部曲可以贯彻整个dp专题。

    • 确定dp数组以及下标的含义

    dp[i]的定义为:第i个数的斐波那契数值是dp[i]

    • 确定递推公式

    状态转移方程 dp[i] = dp[i - 1] + dp[i - 2];

    • dp数组如何初始化

    题目中把如何初始化也直接给我们了,如下:

    dp[0] = 0;
    dp[1] = 1;
    
    • 1
    • 2
    • 确定遍历顺序

    dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的

    • 举例推导dp数组

    Code

    递归法:

    class Solution {
        public int fib(int n) {
            return dfs(n);
        }
        public int dfs(int n){
            if(n==0) return 0;
            if(n==1) return 1;
            return dfs(n-1)+dfs(n-2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    动态规划:

    压缩版本

    class Solution {
        public int fib(int n) {
            if(n < 2) return n;
            int a = 0,b = 1,sum = 0;
            for(int i = 1;i < n;i++){
                sum = a + b;
                a = b;
                b = sum;
            }
            return sum;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    非压缩版本

    class Solution {
        public int fib(int n) {
            if(n<2) return n;
            int[] dp = new int[n+1];
            dp[0] = 0;
            dp[1] = 1;
            for(int index = 2; index<=n; index++){
                dp[index] = dp[index - 1] + dp[index - 2];
            }
            return dp[n];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2. 爬楼梯 - 力扣

    题目链接:70. 爬楼梯 - 力扣(LeetCode)

    思路:与上一道题目思路类似

    Code

    动态规划:

    非压缩版本

    class Solution {
        public int climbStairs(int n) {
            if(n<=2) return n;
            int [] dp = new int[n+1];
            dp[1] = 1;
            dp[2] = 2;
            for(int index = 3;index<=n;index++){
                dp[index] = dp[index-1]+dp[index-2];
            }
            return dp[n];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    压缩版本

    class Solution {
        public int climbStairs(int n) {
            if(n<=2) return n;
            int a = 1,b = 2,num = 0;
            for(int i = 3;i <= n;i++){
                num = a + b;
                a = b;
                b = num;
            }
            return num;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.使用最小花费爬楼梯

    思路

    递归五部曲

    • 确定dp数组以及下标的含义

    dp[i]的定义:到达第i台阶所花费的最少体力为dp[i]。

    • 确定递推公式

    **可以有两个途径得到dp[i],一个是dp[i-1] 一个是dp[i-2]。

    dp[i - 1] 跳到 dp[i] 需要花费 dp[i - 1] + cost[i - 1]。

    dp[i - 2] 跳到 dp[i] 需要花费 dp[i - 2] + cost[i - 2]。

    • dp数组如何初始化

    初始化 dp[0] = 0,dp[1] = 0;

    • 确定遍历顺序

    dp[i]由dp[i-1]dp[i-2]推出,所以是从前到后遍历cost数组

    • 举例推导dp数组

    image-20221128113005287

    Code

    class Solution {
        public int minCostClimbingStairs(int[] cost) {
            int n = cost.length;
            int [] dp = new int [n+1];
            dp[0] = 0;
            dp[1] = 0;
            for(int i = 2;i <= n;i++){
                dp[i] = Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
            }
            return dp[n];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4. 不同路径

    题目链接:62. 不同路径 - 力扣(LeetCode)

    思路:

    • 确定dp数组以及下标的含义

    dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

    • 确定递推公式

    dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来

    • dp数组如何初始化

    如何初始化呢,首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。

    所以初始化代码为:

    for (int i = 0; i < m; i++) dp[i][0] = 1;
    for (int j = 0; j < n; j++) dp[0][j] = 1;
    
    • 1
    • 2
    • 确定遍历顺序

    从左到右,从上到下

    • 举例推导dp数组

    image-20221128121119106

    Code

    动态规划:

    class Solution {
        public int uniquePaths(int m, int n) {
            int[][] dp = new int[m][n];
            for(int i = 0;i < m;i++) dp[i][0] = 1;
            for(int j = 0;j < n;j++) dp[0][j] = 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-1][n-1];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    5.不同路径 II

    题目链接:63. 不同路径 II - 力扣(LeetCode)

    这一题和上一题的区别是,本题多了障碍物!

    题目链接:980. 不同路径 III - 力扣(LeetCode)

    思路:

    • 确定dp数组以及下标的含义

    dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

    • 确定递推公式

    dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来

    if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i][j]
        dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
    }
    
    • 1
    • 2
    • 3
    • dp数组如何初始化

    image-20221128121628055

    如何初始化呢,首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。

    所以初始化代码为:

    for(int i = 0; i < n && obstacleGrid[0][i] == 0; i ++) f[0][i] = 1;
    // 当遇到障碍物时循环就会结束了!
    for(int i = 0; i < m && obstacleGrid[0][i] == 0; i ++) f[i][0] = 1;
    
    • 1
    • 2
    • 3

    注意代码里for循环的终止条件,一旦遇到obstacleGrid[i][0] == 1的情况就停止dp[i][0]的赋值1的操作,dp[0][j]同理

    • 确定遍历顺序

    从左到右,从上到下

    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            if (obstacleGrid[i][j] == 1) continue;
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 举例推导dp数组

    有障碍物的为1

    image-20221128121847385

    Code

    class Solution {
        public int uniquePathsWithObstacles(int[][] obstacleGrid) {
            int m = obstacleGrid.length;
            int n = obstacleGrid[0].length;
            int[][] dp = new int[m][n];
    
            //如果在起点或终点出现了障碍,直接返回0
            if (obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1) {
                return 0;
            }
    
            for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) {
                dp[i][0] = 1;
            }
            for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) {
                dp[0][j] = 1;
            }
    
            for (int i = 1; i < m; i++) {
                for (int j = 1; j < n; j++) {
                    dp[i][j] = (obstacleGrid[i][j] == 0) ? dp[i - 1][j] + dp[i][j - 1] : 0;
                }
            }
            return dp[m - 1][n - 1];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 相关阅读:
    Mysql的存储引擎
    java面试题-学成在线项目
    MySQL存储引擎
    BeanFactory创建流程
    【建造者模式】
    会话跟踪及常用方法
    Linux系统安装Node.js步骤
    数据结构--动态储存顺序表(含完整代码)
    [晕事]今天做了件晕事24;GCC -W
    AWS SAA-C03 #108
  • 原文地址:https://blog.csdn.net/weixin_54040016/article/details/128077450