代码随想录-动态规划-简单题目(Java)

动态规划基础知识

(参考卡哥代码随想录讲解)

什么是动态规划

如果某一问题有很多重叠子问题,使用动态规划是最有效的

所以动态规划中每一个状态一定是由上一个状态推导出来的(这一点就区分于贪心),贪心没有状态推导,而是从局部直接选最优的

动态规划的解题步骤

状态转移公式(递推公式)很重要,但动态规划不仅仅只有递推公式

对于动态规划问题,可以拆解为如下五部曲:

  1. 确定dp数组(dp table)以及下表的含义

  2. 确定递推公式

  3. dp数组如何初始化

  4. 确定遍历顺序

  5. 举例推导dp数组

为什么要先确定递推公式,然后再考虑初始化呢

因为一些情况是递推公式决定了dp数组要如何初始化

后面代码的讲解都会围绕这五点来进行讲解

一. 斐波那契数

链接: 509.斐波那契数列

思路

不用写

代码

//递归
class Solution {
    public int fib(int n) {
        if (n == 0 || n == 1)   return n;
        return fib(n - 1) + fib(n - 2);
    }
}
//用遍历模拟递归
class Solution {
    public int fib(int n) {
        if (n == 0 || n == 1)   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];
    }
}

二、爬楼梯

链接: 70.爬楼梯

思路

思考➕找规律

1阶 1种

2阶 2种

3阶 3种

4阶 5种

5阶 8种

不考虑范围
PS:每次只能走一阶或两阶,逆向思考,第n阶前可能是n - 1阶,也可能是n - 2阶;所以这两种方法相加得到总共方法数
虽然题目简单,但也要明确dp数组的含义,脑海中将五部曲走一遍

dp[i] = dp[i - 1] + dp[i - 2];

代码

class Solution {
    public int climbStairs(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2;i <= n;i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

三、使用最小花费爬楼梯

链接: 746.使用最小花费爬楼梯

思路

还是思考 ➕ 找规律

1.明确dp[i]的含义 到达第i个台阶时消耗的最小花费

2.递推公式

初始位置在0 or 1 的位置,可以每次上一个台阶或两个台阶

自上而下看,我们需要dp[i] 可以从i - 1上一个台阶,也可以从i - 2上两个台阶,需要的代价不同,故:

dp[i] = Math.min(dp[i - 1] + cost[i - 1] , dp[i - 2] + cost[i - 2]);

3.初始化

由递推公式可以看出,我们需要dp[0],dp[1]

4.确定遍历顺序

本题从前向后

5.举例验证

代码

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int[] dp = new int[cost.length + 1];
        dp[0] = 0;
        dp[1] = 0;
        for (int i = 2;i <= cost.length;i++){
            dp[i] = Math.min(dp[i - 1] + cost[i - 1] , dp[i - 2] + cost[i - 2]);
        }
        return dp[cost.length];
    }
}

四、不同路径

链接: 62.不同路径

思路

本题思路挺简单的,是凭借自己能力做出来的哦

按照动态规划五部曲:

1.含义

本题因为是在平面上,所以我们创建二维数组

其实dp【i】【j】表示机器人到达第i行第j列时,总共有多少条路径

2.递推公式

从题意我们可知,机器人每次只能向下或者向右移动一步,在当前位置可由前一位置向下和向右

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

3.初始化

我一开始想的是为了求m,n位置,所以二维数组大小为m + 1,n + 1

机器人一开始的位置在1,1

所以只用求dp在1,1;2,1;1,2;的方法即可

在举例推导时发现不行,i 和 j下标为1的都应该初始化为1

【所以说举例推导很重要!】

4.遍历顺序,从左往右

5.举例推导

代码

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 1;i < m + 1;i++){
            dp[i][1] = 1;
        }
        for (int i = 1;i < n + 1;i++){
            dp[1][i] = 1;
        }
        for (int i = 2;i < m + 1;i++){
            for (int j = 2;j < n + 1;j++){
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[m][n];
    }
}

五、不同路径II

链接: 63.不同路径II

思路

代码算法自己是一点弯儿都不能绕啊

有障碍的话就不走这条路呗

对于初始化时,遇到障碍,之后的路也不能走了,只能是0,不需要再赋值为1

代码

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        int[][] dp = new int[m][n];
        for (int i = 0;i < m && (obstacleGrid[i][0] == 0);i++){
            dp[i][0] = 1;
        }
        for (int i = 0;i < n && (obstacleGrid[0][i] == 0);i++){
            dp[0][i] = 1;
        }
        for (int i = 1;i < m;i++){
            for (int j = 1;j < n;j++){
                if (obstacleGrid[i][j] == 0)
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[m - 1][n - 1];
    }
}

六、整数拆分

链接: 343.整数拆分

思路

本题不是那种一眼看出解法的问题

如果按照五部曲,其实是可以做出来的

1.含义

一般这个含义与题目有着直接的关系

dp[i] 在本题中指 i 被拆分后的最大乘积

2.递推公式

直接关系

dp[i] = j * dp[i - j]

本题求最大乘积

dp[i] = Math.max(dp[i],j * (i - j),j * dp[i - j]);

3.初始化

n,从2开始的

dp[0]= 0

dp[1] = 0

dp[2] = 1

4.遍历顺序

从左向右遍历

5.举例推导

代码

class Solution {
    public int integerBreak(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 0;
        dp[2] = 1;
        for (int i = 3;i < n + 1;i++){
            for (int j = 1;j <= i / 2;j++){
                dp[i] = Math.max(dp[i],Math.max(j * (i - j),j * dp[i - j]));
                // 第二个Math.max()应注意。
                // j * (i - j) 是单纯的把整数 i 拆分为两个数 也就是 i,i-j ,再相乘
                //而j * dp[i - j]是将 i 拆分成两个以及两个以上的个数,再相乘。
            }
        }
        return dp[n]; 
    }
}

七、不同的二叉搜索树

链接: 96.不同的二叉搜索树

思路

本题属于我的盲区

1.dp[i]含义

dp[i]:指 i 个节点组成符合条件的二叉搜索树的种类

2.递推公式

【没想到】

观察n= 3的情况

1 开头,2开头 3开头

1开头的情况:

左0 * 右2

2开头的情况:

左1 * 右1

3开头的情况:

左2 * 右2

然后可以得出来

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

1 <= j <= i

3.初始化

dp[0] = 1;(题目要求的范围不包含,为了便于计算)

dp[1] =1;

4.遍历顺序:正常从左向右的顺序

5.举例推导:略

代码

class Solution {
    public int numTrees(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 1;  
        dp[1] = 1;
        for (int i = 2;i < n + 1;i++){
            for (int j = 1;j <= i;j++){
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }
        return dp[n];
    }
}

总结

以上,除不同路径两道题外,即使循环是双重循环,但dp数组也都是一位数组,j的循环是一种辅助

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值