其实动态规划对于我最难的是边界问题
目录
1.动态规划四部曲:
1.确定状态,确定从什么时候开始,就是amout
2.确定转移方程,也就是 if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);//变成子问题,问题规模缩小
}
3.确定初始条件和边界条件,也就是前面和后面几步
4.确定步数和尝试次数,也就是i和j的范围
class Solution {
public int coinChange(int[] coins, int amount) {
int max = amount + 1;//设置max填充dp数列,为之后的边界问题做铺垫
int[] dp = new int[amount + 1];//定义dp长度,因为dp是从1开始,所以需要多一格
Arrays.fill(dp, max);//填充数列
dp[0] = 0;//dp[0]设置值,为特殊情况做准备
for (int i = 1; i <= amount; i++) {//一步步递进
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);//变成子问题,问题规模缩小
}
}
}
return dp[amount] > amount ? -1 : dp[amount];//判断是否正好组成总金额
}
}
迷宫问题
class Solution {
public int uniquePaths(int m, int n) {
int dp[][]=new int[m][n];
for(int i=0;i<n;i++){
//先设置零行的路径方法都为1
dp[0][i]=1;
}
for(int i=0;i<m;i++){
//零列的路径方法也都为1,如此设置是防止后面的数组溢出,也就是设置边界条件
dp[i][0]=1;
}
if(m==0&&n==0){
return 0;
}
for (int i=1;i<m;i++){//设置i和j的边界
for (int j = 1; j <n ; j++) {
dp[i][j]=dp[i-1][j]+dp[i][j-1];//核心方程式,与斐波那契数列相似
}
}
return dp[m-1][n-1];
}
}
最大数组子序列和问题
class Solution {
public int maxSubArray(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = nums[0];
int max = dp[0];
for(int i=1;i<nums.length;i++){
dp[i]=Math.max(dp[i-1]+nums[i],nums[i]);
max=Math.max(max,dp[i]);
}
return max;
}
}
类似于上题,也是在之前走过的地方进行标记,从而免去一系列重复的计算,将单词分成各个部分,逐步确认能否拆分,如果一部分能拆分,则将其置为True
单词拆分
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
// 可以类比于背包问题
int n = s.length();
// memo[i] 表示 s 中以 i - 1 结尾的字符串是否可被 wordDict 拆分
//个人感觉像记忆化搜索,对所走的路进行标记
boolean[] memo = new boolean[n + 1];
memo[0] = true;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
//memo[j]为true表明之前的都已经匹配上了
if (memo[j] && wordDict.contains(s.substring(j, i))) {
memo[i] = true;
break;
}
}
}
return memo[n];
}
}
动态规划之最长回文子串
设置起点为start,设置终点为END,如何判断起点为Start,终点为END的字符串是回文子串呢,设计标记数组arr,只要arr[start+1][end-1]为true,即子串是回文子串,并且两端的字符一样,就表示是回文子串,同时要考虑边界条件,字符串为2个或者3个的情况下,判读是否为回文子串,在长度为2或3时,只需要判断两端字符是否相等即可。用到了动态规划的思想,将问题规模一步步化简。
class Solution {
public String longestPalindrome(String s) {
boolean arr[][]=new boolean[s.length()][s.length()];
if(s.length()==1){
return s;
}
int max=0;
int Start=0;
int End=0;
for(int right=1;right<s.length();right++){
for (int left=0;left<right;left++){
if (s.charAt(left)==s.charAt(right)&&(right-left<=2||arr[left+1][right-1])){
arr[left][right]=true;
if(max<right-left+1){
Start=left;
End=right;
max=End-Start+1;
}
}
}
}
return s.substring(Start,End+1);
}
}
309. 最佳买卖股票时机含冷冻期
这题将dp定义了四种状态
1.日期
2.持股
3.不持股
4.在冷冻期(即上一天卖出股票)
通过对状态的分析,求出各个的局部最优解,最终得到最优解
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) {
return 0;
}
//由于可以无限次交易,所以只定义两个维度,第一个维度是天数,第二个维度表示是否持有股票,0表示不持有,1表示持有,2表示过渡期
int[][] dp = new int[prices.length][3];
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
for (int i = 1; i < prices.length; i++) {
//第i天不持有股票的情况有两种
//a.第i-1天也不持有股票
//b.第i-1天是过渡期
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2]);
//第i天持有股票有两种情况
//a.第i-1天也持有股票,第i天不操作,
//b.第i-1天不持有股票,在第i天买入
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i]);
//第i天是冷冻期只有一种情况,第i-1天持有股票且卖出
dp[i][2] = dp[i-1][1] + prices[i];
}
//最后最大利润为最后一天,不持有股票或者进入冷冻期的情况
return Math.max(dp[prices.length-1][0], dp[prices.length-1][2]);
}
}
树形DP
树形DP便是维护两个状态数组,分别为left[]和right[],表示左子节点和右子节点,数组中又存放状态,在此题中,存放小偷偷或者不偷两种状态,采取后续遍历,用动态规划进行求解。这里的动态规划采取了递归的形式。
class Solution {
// 3.状态标记递归
// 执行用时:0 ms , 在所有 Java 提交中击败了 100% 的用户
// 不偷:Max(左孩子不偷,左孩子偷) + Max(又孩子不偷,右孩子偷)
// root[0] = Math.max(rob(root.left)[0], rob(root.left)[1]) +
// Math.max(rob(root.right)[0], rob(root.right)[1])
// 偷:左孩子不偷+ 右孩子不偷 + 当前节点偷
// root[1] = rob(root.left)[0] + rob(root.right)[0] + root.val;
public int rob(TreeNode root) {
int[] res = robAction1(root);
return Math.max(res[0], res[1]);
}
int[] robAction1(TreeNode root) {
//0表示不偷当前结点,1表示偷当前节点
int res[] = new int[2];
if (root == null)
return res;
int[] left = robAction1(root.left);
int[] right = robAction1(root.right);
//不偷当前节点
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
//偷当前节点(当前节点+左右孩子不偷)
res[1] = root.val + left[0] + right[0];
return res;
}
}
背包问题
背包问题的原型 他总结的非常好
总之背包类的动态规划问题,个人来是存在一个约束条件的。在典型背包中,他的约束条件是背包的容量,通过背包的容量,和物品的价值,通过两层for循环,逆序的求得背包所能取得物品的最大价值。
分割等和子集也是背包问题的变形 力扣题目链接
所要求的值为数组和的一半,那我们就以此为约束,抽象成背包的容量,求得在此容量下所能承载的最大价值,再对比约束条件,就能求得结果!