动态规划五步

问题 解决
下标/数组的含义 一般是容量
递推公式 找规律
初始化 看条件
遍历数组 2层for循环要注意谁在外
打印数组 查错

当时做的时候,有点问题

整数拆分

给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。
返回 你可以获得的最大乘积 。
示例 1:

输入: n = 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:

输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

一个整数怎么拆才能乘积最大 比如 3 -> 2|1 21=2 4-> 2|2 22=4
遍历一个一个的拆 将一个数先拆成 1 和 n-1 再试试 2 和 n-2
dp[i]=Math.max(Math.max(j*(i-j),j*dp[i-j]),dp[i])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
/**分析
1.平分乘积最大
*/
public int integerBreak(int n) {
//下标代表这个数被拆分后乘积的最大值
//dp[i]
int[] dp=new int[n+1];
dp[2]=1;
for(int i=3;i<=n;i++){
for(int j=1;j<i;j++){
//一个数分为j 和 i-j 它
dp[i]=Math.max(Math.max(j*(i-j),j*dp[i-j]),dp[i]);
}
}
return dp[n];
}
}

不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
图片
三个节点的 累加 左面节点数从0涨到3-1个累加 如果左边节点数是2个的话 则有dp[2]个 同理右边也是 左右两边相乘
dp[i]+=dp[j]*dp[i-j-1];

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int numTrees(int n) {
int[] dp=new int[n+1];
//递推公式 dp+=dp[j]*dp[i-j-1];
dp[0]=1;dp[1]=1;
for(int i=2;i<=n;i++){
for(int j=0;j<i;j++){
dp[i]+=dp[j]*dp[i-j-1];
}
}
return dp[n];
}
}

分割等和子集

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

二维dp数组

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
27
28
class Solution {
public boolean canPartition(int[] nums) {
int sum=0;
for(int i=0;i<nums.length;i++)sum+=nums[i];
if(sum%2!=0)return false;
//背包问题,定义dp数组 i为有多少个数 j为有容量为多大的背包 加一是为了有
//0容量和0物品
int[][] dp=new int[nums.length+1][sum/2+1];
//初始化 但是new的时候就已经初始化了 是初始化dp数组
//dp[0]这一排全为0 dp[i][0]这一列全为0
for(int i=0;i<nums.length+1;i++)dp[i][0]=0;
for(int i=0;i<sum/2+1;i++)dp[0][i]=0;
//开始赋值
for(int i=1;i<nums.length+1;i++){
for(int j=1;j<sum/2+1;j++){
//如果背包容量大于这个数值就可以放
if(nums[i-1]>j){
//不取这个数
dp[i][j]=dp[i-1][j];
}else{
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-nums[i-1]]+nums[i-1]);
if(dp[i][j]==sum/2)return true;
}
}
}
return dp[nums.length][sum/2]==sum/2;
}
}

一维dp数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public boolean canPartition(int[] nums) {
//利用一维数组写一下
//定义一维数组
int sum=0;
for(int i=0;i<nums.length;i++)sum+=nums[i];
//奇数和都无法求平均数 直接返回false
if(sum%2==1)return false;
int[] dp=new int[sum/2+1];//注意背包容量必须下标有等于背包容量的
//初始化 下标为0时是没有任何东西存入的
for(int i=0;i<nums.length;i++)
for(int j=dp.length-1;j>=nums[i];j--){
dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]);
//这里可以直接判断是不是等于sum/2 如果等于可以直接返回true
if(dp[j]==sum/2)return true;
}
return false;
}
}

图片

目标和

给你一个非负整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 ‘+’ 或 ‘-‘ ,然后串联起所有整数,可以构造一个 表达式 :

例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-‘ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
图片

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
class Solution {
/**
正数集合-负数集合=target
负数集合=sum-正数集合
所以 正数集合=(sum+target)/2;
我们找到有多种可以凑成正数集合的方法 就是找到了有多少种可以得到target的·方法
*/
public int findTargetSumWays(int[] nums, int target) {
//求和
int sum=0;
for(int i=0;i<nums.length;i++)sum+=nums[i];
if(sum<Math.abs(target))return 0;//全部取负数和全部取正数都不满足
if((sum+target)%2==1)return 0;//因为2倍的正正数=sum+target
int n=(sum+target)/2;
//定义dp数组 dp[j] 大小为j时有dp[j]种方法可以实现
//dp[j]=(有j-1这个数时)dp[1]
int[] dp=new int[n+1];
dp[0]=1;
//for(int i=0;i<nums.length;i++)dp[1]++;
for(int i=0;i<nums.length;i++)
for(int j=n;j>=nums[i];j--)
dp[j]+=dp[j-nums[i]];

return dp[n];
}
}

1和0 474

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

示例 1:

输入:strs = [“10”, “0001”, “111001”, “1”, “0”], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {“10”,”0001”,”1”,”0”} ,因此答案是 4 。
其他满足题意但较小的子集包括 {“0001”,”1”} 和 {“10”,”1”,”0”} 。{“111001”} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。
示例 2:

输入:strs = [“10”, “0”, “1”], m = 1, n = 1
输出:2
解释:最大的子集是 {“0”, “1”} ,所以答案是 2 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
//i表示0 j表示1 dp[i][j]表示有i个0 和 j个1有多少种方法
int[][] dp=new int[m+1][n+1];
//递推公式 dp[i][j]=Math.max(dp[i][j],dp[i-x][j-y]+1);
for(String s:strs){
int x=0,y=0;
for(int i=0;i<s.length();i++)
if(s.charAt(i)=='0')
x++;
else y++;
for(int i=m;i>=x;i--){
for(int j=n;j>=y;j--){
dp[i][j]=Math.max(dp[i][j],dp[i-x][j-y]+1);
}
}
}
return dp[m][n];
}
}

单词拆分

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例 1:

输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以由 “leet” 和 “code” 拼接成。
示例 2:

输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以由 “apple” “pen” “apple” 拼接成。
注意,你可以重复使用字典中的单词。
示例 3:

输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
//背包容量为单词的长度;i->dp[i] 有没有满足拼接的单词
//if(list里面`有截取的单词&&dp[i-这个截取的长度]也有)dp[i]=true;
boolean[] dp=new boolean[s.length()+1];
//初始化 为什么dp[0]=true 因为dp[i]是要靠前面递推而来
dp[0]=true;
//开始遍历 排列->先背包 因为是截取来判断
for(int i=1;i<s.length()+1;i++)
for(int j=0;j<i;j++)
if(wordDict.indexOf(s.substring(j,i))!=-1&&dp[j])
dp[i]=true;

return dp[s.length()];
}
}

打家劫舍Ⅲ

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。

示例 1:
图片

输入: root = [3,2,3,null,3,null,1]
输出: 7
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7
示例 2:

图片

输入: root = [3,4,5,1,3,null,1]
输出: 9
解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9

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
27
28
29
30
31
32
33
34
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//此节点 取 | 不取
public int rob(TreeNode root) {
int[] dp=a(root);
return Math.max(dp[0],dp[1]);
}
private int[] a(TreeNode root){
int[] dp=new int[2];//0表示不取 1表示取
if(root==null)return dp;

int[] left=a(root.left);
int[] right=a(root.right);

dp[1]=root.val+left[0]+right[0];
dp[0]=Math.max(left[0],left[1])+Math.max(right[0],right[1]);

return dp;
}
}

买卖股票的最佳时机Ⅲ

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。
示例 4:

输入:prices = [1]
输出:0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int maxProfit(int[] prices) {
int n=prices.length;
int[][] dp=new int[n][5];
dp[0][0]=0;//不操作
dp[0][1]=-prices[0];//第一次持有
dp[0][2]=0;//第一次不持有
dp[0][3]=-prices[0];//第二次持有
dp[0][4]=0;//第二次不持有
for(int i=1;i<n;i++){
dp[i][1]=Math.max(dp[i-1][0]-prices[i],dp[i-1][1]);//前一天持有|前一天不操作这一天持有
dp[i][2]=Math.max(dp[i-1][2],dp[i-1][1]+prices[i]);//前一天不持有|前一天持有这一天卖出
dp[i][3]=Math.max(dp[i-1][3],dp[i-1][2]-prices[i]);//前一天第二次持有|前一天第二天不操作(也就是第一次卖出后)这一天买入
dp[i][4]=Math.max(dp[i-1][4],dp[i-1][3]+prices[i]);//前一天第二次卖出|前一天第二次没有卖出今天卖出
}
return dp[n-1][4];
}
}

买卖股票得最佳时机Ⅳ

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入:k = 2, prices = [2,4,1]
输出:2
解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
示例 2:

输入:k = 2, prices = [3,2,6,5,0,3]
输出:7
解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int maxProfit(int k, int[] prices) {
//买卖k次
int n=prices.length;
int[][] dp=new int[n][k*2+1];
//dp[x][奇数]为持有
//dp[x][偶数]为不持有
for(int i=1;i<k*2+1;i+=2)dp[0][i]=-prices[0];//对其进行初始化
for(int i=1;i<n;i++){
for(int j=1;j<k*2+1;j+=2){
//前一天的不操作+今天买入 | 继续前一天的持有
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-1]-prices[i]);
//前一天的持有+今天卖出 | 继续前一天的不持有
dp[i][j+1]=Math.max(dp[i-1][j+1],dp[i-1][j]+prices[i]);
}
}
return dp[n-1][k*2];
}
}

买卖股票的最佳时机含冷冻期

给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: prices = [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
示例 2:

输入: prices = [1]
输出: 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public int maxProfit(int[] prices) {
int n=prices.length;
int[][] dp=new int[n][4];
dp[0][0]=-prices[0];//持有股票 它的前一天可以是冷冻期 或者是保持卖出的状态
dp[0][1]=0;//保持卖出得状态 它的前一天可以是冷冻期
dp[0][2]=0;//具体卖出的状态 它的前一天必须是持有股票
dp[0][3]=0;//冷冻期
for(int i=1;i<n;i++){
//保持前一天的持有状态|买入(前一天是冷冻期,前一天是保持卖出的状态)
dp[i][0]=Math.max(dp[i-1][0],Math.max(dp[i-1][3]-prices[i],dp[i-1][1]-prices[i]));
//前一天卖出的状态 | 前一天冷冻期
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][3]);
//前一天持有 今天卖出
dp[i][2]=dp[i-1][0]+prices[i];
//前一天买出
dp[i][3]=dp[i-1][2];
}
//最后一天必须是卖出的状态
return Math.max(dp[n-1][1],Math.max(dp[n-1][2],dp[n-1][3]));
}
}