大家知道动规是由前一个状态推导出来的,而贪心是局部直接选最优的,对于刷题来说就够用了。
对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
part 02
不同路径
63. 不同路径 II - 力扣(LeetCode)
//初始化(优化)for(int j=1;j<=n && obstacleGrid[0][j-1]==0;j++){dp[1][j]=1;}for(int i=1;i<=m && obstacleGrid[i-1][0]==0 ;i++){dp[i][1]=1;}
part 03
343. 整数拆分 - 力扣(LeetCode)
- dp[i] 拆分数字i,得到最大乘积dp[i]
- 递推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j});
也可以这么理解,j * (i - j) 是单纯的把整数拆分为两个数相乘,而j * dp[i - j]是拆分成两个以及两个以上的个数相乘。
那么在取最大值的时候,为什么还要比较dp[i]呢?
因为在递推公式推导的过程中,为了每次计算更新dp[i] - 初始化 dp[2]=1
- 确定遍历顺序
dp[i] 是依靠 dp[i - j]的状态,所以遍历i一定是从前向后遍历,先有dp[i - j]再有dp[i]。
public int integerBreak(int n) {int[] dp = new int[n+1];dp[2]=1;for(int i=3;i<=n;i++){for(int j=1;j<=i-1;j++){dp[i]=Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));}}return dp[n];}
96. 不同的二叉搜索树 - 力扣(LeetCode)
当1为头结点的时候,其右子树有两个节点,看这两个节点的布局,是不是和 n 为2的时候两棵树的布局是一样的啊!
(可能有同学问了,这布局不一样啊,节点数值都不一样。别忘了我们就是求不同树的数量,并不用把搜索树都列出来,所以不用关心其具体数值的差异)
当3为头结点的时候,其左子树有两个节点,看这两个节点的布局,是不是和n为2的时候两棵树的布局也是一样的啊!
当2为头结点的时候,其左右子树都只有一个节点,布局是不是和n为1的时候只有一棵树的布局也是一样的啊!
发现到这里,其实我们就找到了重叠子问题了,其实也就是发现可以通过dp[1] 和 dp[2] 来推导出来dp[3]的某种方式。
思考到这里,这道题目就有眉目了。
part04
背包问题
0 1背包
求容量为j的背包,最多能装多少(二维数组)
能不能放
- 不能
取上一个值 - 能
取放和不放的最大值
import java.util.ArrayList;
import java.util.Scanner;public class Main {public static void main(String[] args) {int m;int n;Scanner sc=new Scanner(System.in);m=sc.nextInt();//材料种类n=sc.nextInt();//行李空间int[] weight=new int[m];int[] value=new int[m];for (int i = 0; i < m; i++) {weight[i]=sc.nextInt();}for (int i = 0; i < m; i++) {value[i]=sc.nextInt();}//dp[i][j] i表示第i种材料,j表示行李容量,dp[i][j]表示前i种材料放入j空间的最大价值int[][] dp=new int[m][n+1];//递推公式 取放i材料,与不放i材料的value最大值 dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])//初始化 由递推公式可知,每一个值由左上方的值推导而来.因此初始化第一行第一列for(int j=weight[0];j<=n;j++){dp[0][j]=value[0];}//遍历顺序 从前往后for(int i=1;i<m;i++){for(int j=1;j<=n;j++){if (j < weight[i]) {/*** 当前背包的容量都没有当前物品i大的时候,是不放物品i的* 那么前i-1个物品能放下的最大价值就是当前情况的最大价值*/dp[i][j] = dp[i-1][j];} else {/*** 当前背包的容量可以放下物品i* 那么此时分两种情况:* 1、不放物品i* 2、放物品i* 比较这两种情况下,哪种背包中物品的最大价值最大*/dp[i][j] = Math.max(dp[i-1][j] , dp[i-1][j-weight[i]] + value[i]);}}}System.out.println(dp[m-1][n]);}}
416. 分割等和子集 - 力扣(LeetCode)
分成两个,背包大小为sum/2
part 05
1049. 最后一块石头的重量 II - 力扣(LeetCode)
本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。
return int
装满容量为x的背包,有几种方法
494. 目标和 - 力扣(LeetCode)
本题要如何使表达式结果为target,
正数集合和left-负数集合和right=target
left + right = sum,而sum是固定的。right = sum - left
公式来了, left - (sum - left) = target 推导出 left = (target + sum)/2 。
target是固定的,sum是固定的,left就可以求出来。
此时问题就是在集合nums中找出和为left的组合。
此时问题就转化为,装满容量为x的背包,有几种方法n。
之前都是求容量为j的背包,最多能装多少。
return n
474. 一和零 - 力扣(LeetCode)
class Solution {public int findMaxForm(String[] strs, int m, int n) {// dp[i][j] i个0,j个1大的容器,最多装元素 dp[i][j]个//dp[i][j] = Math.max(dp[i][j],dp[i-x][j-y]+1);,x表示装strs[i] 中0的个数古,y表示strs[i] 中1的个数int zeroNums=0;int oneNums=0;int[][] dp=new int[m+1][n+1];for(String str:strs){zeroNums=0;oneNums=0;for(char ch: str.toCharArray()){if(ch == '0'){zeroNums++;}else{oneNums++;}}//倒序遍历for(int i=m;i>=zeroNums;i--){for(int j=n;j>=oneNums;j--){dp[i][j]=Math.max(dp[i][j],dp[i-zeroNums][j-oneNums]+1);}}}return dp[m][n];}}
没太搞懂
//倒序遍历for(int i=m;i>=zeroNums;i--){for(int j=n;j>=oneNums;j--){dp[i][j]=Math.max(dp[i][j],dp[i-zeroNums][j-oneNums]+1);}}
未完待续