今日题目
知识点介绍:
- 动态规划和贪心算法的直观区别:
1)动态规划:后一步需要依赖于前一步的操作
2)贪心:后一步与前一步选择无关,每一步都需要达到最优,才能得到全局最优。 - 动态规划的解题步骤:
1)确定dp数组(dp [ i ] )以及下标的含义 :
2)确定递推公式
3)dp数组如何初始化
4)确定遍历顺序
5)举例推导dp数组
题目一:509. 斐波那契数
- 题目说明
- 求解思路
1)可以使用递归的方法去做
2)可以使用动态规划的方法做,得到一个一维的数组,每个位置装着索引n对应的斐波那契数。 - 求解步骤
1)首先判断dp[i]数组的含义是什么:dp[i],f(n)的菲波那切数列。
2)得到dp数组的迭代式:
3)dp数组的初始化
4)dp数组的遍历方式
5)举一个例子进行操作。 - 代码展示
//采用递归的方法
public int fib(int n) {
if(n<=1) return n;
return fib(n-1)+fib(n-2);
}
//采用动态规划:使用一个矩阵
public int fib(int n) {
if(n<=1) return n;
//确定dp数组的含义
int[] dp=new int[n+1];
//初始化
dp[0]=0;
dp[1]=1;
//遍历顺序+递推公式
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
System.out.println("dp[n]="+Arrays.toString(dp));
return dp[n];
}
- 补充说明
按照步骤一步一步来就行了。
题目二:70. 爬楼梯
-
题目说明
-
求解思路
爬楼梯, -
求解步骤
-
代码展示
//这个题目就是斐波那契的翻版
//步骤一:dp[i]的含义是:爬到第i层,有多少种方法
//步骤二:确认递推公式:dp[i]=dp[i-1]+dp[i-2];
//步骤三:初始化,i从一开始,故只需要讨论dp[1]=1和dp[2]=2
//步骤四:遍历的顺序,从前向后遍历
//步骤五:自己举例说明
if(n<=2) return n;
//初始化:
int[] dp=new int[n+1];
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
- 补充说明
题目三:746. 使用最小花费爬楼梯
- 题目说明
- 求解思路
- 求解步骤
- 代码展示
public int minCostClimbingStairs(int[] cost) {
//dp[i]的含义:在步骤i位置的最小花费
//初始化
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.不同路径
- 题目说明
- 求解思路
- 求解步骤
- 代码展示
public int uniquePaths(int m, int n) {
//这需要用一个二维数组装:dp[i][j]每个位置的最大路径数
//初始化:
int[][] dp=new int[m][n];
for(int i=0;i<n;i++){
dp[0][i]=1;
}
for(int j=0;j<m;j++){
dp[j][0]=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];
}
}
System.out.println("dp="+Arrays.deepToString(dp));
return dp[m-1][n-1];
}
- 补充说明
题目五:63. 不同路径 II
- 题目说明
- 求解思路
- 求解步骤
- 代码展示
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
//与不同路径问题的区别就是,当出现障碍的地方,路径数变为0
//这需要用一个二维数组装:dp[i][j]每个位置的最大路径数
//初始化:
int m=obstacleGrid.length;
int n=obstacleGrid[0].length;
int[][] dp=new int[m][n];
for(int i=0;i<n;i++){ //注意点:如果在初始化的位置出现障碍,后面也都为0
if(i==0) {
dp[0][i]=obstacleGrid[0][i]==1?0:1;
}else{
dp[0][i]=obstacleGrid[0][i]==1?0:dp[0][i-1];
}
}
for(int j=1;j<m;j++){
dp[j][0]=obstacleGrid[j][0]==1?0:dp[j-1][0];
}
System.out.println("dp="+Arrays.deepToString(dp));
//遍历顺序
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=obstacleGrid[i][j]==1?0:dp[i-1][j]+dp[i][j-1];
}
}
System.out.println("dp="+Arrays.deepToString(dp));
return dp[m-1][n-1];
}
- 补充说明
题目六:343. 整数拆分
- 题目说明
![](https://img-blog.csdnimg.cn/a0f68ff613f14b45a3dda6afee08d638.png#pic_center)
- 求解思路
这道题可以作为一道数学题目,用到了贪心的思想,每一步,尽可能多的三 ,就能够达到最大。 - 求解步骤
1)当数字小于四的时候,可以直接得出结果
2)当数字大于四的时候,尽可能多的三,然后剩余的留下来。
3)以上的结论可以通过自己举例子观察得到。 - 代码展示
//贪心算法
public int integerBreak(int n) {
//纯数学方法,尽可能多3,在数大于四的时候
if(n<=3) return n-1;
//
int a=1;
while(n>4){
n=n-3;
a=a*3;
}
return a*n;
}
- 补充说明
纯粹的数学方法求解,当数大于四的时候,尽可能多的三。
题目七:96.不同的二叉搜索树
- 题目说明
- 求解思路
对于动规这类题目,首先是找到动规表达式的意义:
1)dp[i]表示i个节点组成不同二叉搜索树的个数
2)然后就去试一试,dp[0],dp[1],dp[2].dp[3]分别为多少,推导出表达是式子。
3)dp[0]=1; dp[1]=1 ; dp[2] =2 ;dp[3] =dp[0]*dp[2]+dp[1]*dp[1]+dp[2]*dp[0] 故公式就出来了
4)dp[i]=dp[0]*dp[i-1]+dp[1]*dp[i-2]+dp[2]*dp[i-2]+…dp[i-1]*dp[0]; - 求解步骤
1)推测dp数组的含义
2)推导dp数组的迭代式
3)dp数组的初始化操作
4)dp数组的遍历方式
5)拿一个案例测试一下 - 代码展示
class Solution {
public int numTrees(int n) {
//1)特殊情况先处理
if(n<=2) return n;
//2)dp数组初始化
int[] dp=new int[n+1];
dp[0]=1;
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++){
for(int j=0;j<=i-1;j++){
dp[i]+=dp[j]*dp[i-1-j];
}
}
return dp[n];
}
}
- 补充说明
1)稿明白dp数组的意义;
2)还有就是找规律是核心;可以通过一个个例子来推导公式。(不需要证明)
总结:动态规划基本步骤
- 今天这几道题目都是比较简单的,所以可能用不到动规的五步曲,但是一定要牢记,后续的题目一定会用上的。
- 动规五步走
1)dp定义:确定dp数组(dp [ i ] ,dp[ i ][ j ])以及下标的含义
2)dp数组的递归表达式:根据前者如何推断出后者的情况。
3)dp数组初始化:最初始的几个情况是什么?
4)dp数组的遍历方式:怎么遍历?
5)举例推导dp数组:自己举一个简单的例子,看看迭代过程是不是自己期望的。 - 代码写出来出错怎么办?灵魂三问
1)这道题我举例推导出递推公式了吗?
2)我打印出来中间的dp数组日志了吗?
3)我打印出来的日志和我期望中的日志是不是一样的?