0.前言
动态规划的步骤是什么?
1.找到状态、选择。状态,是在变化的东西;选择,是你可以选择的东西。
2.明确dp数组的含义
3.寻找状态转移方程
4.如何选择遍历方向?遍历时的2个注意事项:(1)、所需状态必须是已经计算出来的;(2)、遍历的终点必须是存储返回结果的位置。
1. 让字符串成为回文串的最少插入次数
1.题目分析
- 1.dp[][]数组,dp[i][j]表示第 i 到 j 的元素变为回文串所需最短的步骤;
- 2.base case,i==j时,dp[i][j] = 0; i > j时,dp[i][j] = 0;
- 3.状态转移方程,进行反向遍历,当s[i] == s [j] 时,dp[i][j] = dp[i+1][j-1];
否则,dp[i][j] = 1 + min(dp[i+1][j] , dp[i][j-1],);
即,选择前者在右边添加一个s[i] 和 后者在左边添加1个s[j],步数最小的那个。 - 4.最终状态存在dp[0][len-1]中。
2.代码
class Solution {
public int minInsertions(String s) {
if(s == null || s.length() <= 1)
return 0;
int len = s.length();
1.定义dp数组
int[][] dp = new int[len][len];
2.base case,二维数组下三角全为0
3.状态转移方程 分析知,进行反向遍历,因为所需状态必须是已经计算出来的
for (int i = len-2; i >= 0; i--) {
for (int j = i+1; j < len; j++) {
if (s.charAt(i) == s.charAt(j)){
dp[i][j] = dp[i+1][j-1];
}else {
选择在右边添加一个s[i] 或者 在左边添加1个s[j]
dp[i][j] = 1 + Math.min(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][len-1];
}
}
2. 戳气球
1.题目分析
- 1.dp[][]数组,dp[i][j]表示第i个和第j个气球之间的可以得到多少硬币(不包含i、j自身);
- 2.扩充气球数组nums变为point数组,即nums左右两边分别添加1个虚拟气球,数字为1;
- 3.base case,当i == j时,dp[i][j] == 0,
- 当i > j 时,dp[i][j] == 0,
- 当i+1 == j 时,dp[i][j] == 0,
- 4.状态转移方程,反向遍历,在i和j之间做选择,最后一个被戳得是其中哪一个?假设是第k个,那么
- dp[i][j] = Max( dp[i][j], dp[i][k] + dp[k][j] + point[i]*point[k]*point[j] )
2.代码
class Solution {
public int maxCoins(int[] nums) {
int len = nums.length;
为方便计算,在nums左右各自增加1个虚拟气球
int[] point = new int[len+2];
point[0] = 1;
point[len+1] = 1;
for (int i = 0; i < len; i++) {
point[i+1] = nums[i];
}
1.定义动态数组
int[][] dp = new int[len+2][len+2];
2.base case
i+1 >= j dp[i][j]==0
3.状态转移方程
for (int i = len-1; i >= 0; i--) {
for (int j = i+1; j < len+2; j++) {
做选择,到底最后被戳得是 i、j 之间的哪一个?
for (int k = i+1; k <= j-1; k++) {
dp[i][j] = Math.max(dp[i][j], 维持?
dp[i][k] + dp[k][j] + point[i]*point[k]*point[j]); 还是更新?
}
}
}
return dp[0][len+1];
}
}
3. 分割等和子集
1.题目分析
- 1.dp数组,dp[i][j]表示,对于前i个数字,如果存在子序列sum和可以是j,那么为true,否则false;
- 2.base case,dp[0][…] == fasle,0个数字无法组成和为其他数字(当然除0之外)
- dp[…][0] == true,只要1个数字不选就能组成和为0.
- 3.最终状态dp[len][sum/2];
- 4.状态转移方程,正向遍历。
2.代码
class Solution {
public boolean canPartition(int[] nums) {
int len = nums.length;
int sum = 0;
for (int i = 0; i < len; i++) {
sum += nums[i];
}
if (sum % 2 ==1)
return false;
sum /= 2;
1.动态数组
boolean[][] dp = new boolean[len+1][sum+1];
2.base case
for (int i = 0; i < sum+1; i++) {
dp[0][i] = false;
}
for (int i = 0; i < len+1; i++) {
dp[i][0] = true;
}
3.状态转移方程
for (int i = 1; i < len+1; i++) {
for (int j = 1; j < sum+1; j++) {
1.如果背包还能装下nums[i-1]时
if (j - nums[i-1] >= 0){
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];
}
2.背包不能装下nums[i-1]时
else
dp[i][j] = dp[i-1][j];
}
}
return dp[len][sum];
}
}
4. 零钱兑换
1.题目分析
1.选择:哪种面值的硬币
2.状态:amount-已选硬币后的剩余值
3.dp数组,dp[i]表示要凑成i元需要多少硬币数;
4.状态转移
2.代码
class Solution {
public int coinChange(int[] coins, int amount) {
if (amount < 0) return -1;
if (amount == 0) return 0;
1.dp[i] 表示凑成i,最少需要多少硬币
int[] dp = new int[amount+1];
Arrays.fill(dp, amount+1);
2.base case
dp[0] = 0;
3.状态转移方程
凑成的target金额,状态变化
for (int i = 0; i <= amount; i++) {
对硬币做选择
for (int j = 0; j < coins.length; j++) {
如果当前硬币可以选
if (i - coins[j] >= 0){
dp[i] = Math.min( dp[i], dp[i-coins[j]]+1 );
}
}
}
return dp[amount]==amount+1 ? -1 : dp[amount];
}
}
5. 零钱兑换 II
1.题目分析
-
1.dp动态数组,dp[i][j] 表示前i个面值的硬币凑成数额j的数量
-
2.base case :
-
dp[0][..] == 0 ,0个硬币什么都凑不成(除了0元); dp[..][0] == 1 ,凑成0,只需要什么都不出,就行
-
3.状态转移方程:正向遍历
-
if(j-coins[i-1] >= 0) // 第i个硬币可以使用时 //前i-1个硬币组成金额j的数量 + 前i-1个硬币组成金额j-coins[i-1]的数量 dp[i][j] = dp[i-1][j] + dp[i-1][j-coins[i-1]] else // 第i个硬币不能使用时 dp[i][j] = dp[i-1][j]
-
4.目标求dp[len][amount]
2.代码
class Solution {
public int change(int amount, int[] coins) {
int len = coins.length;
1.定义dp数组
int[][] dp = new int[len+1][amount+1];
2.base case
for (int i = 0; i < len+1; i++) {
dp[i][0] = 1;
}
3.状态转移方程
for (int i = 1; i < len+1; i++) {
for (int j = 1; j < amount+1; j++) {
此时可以做选择,用或不用第i个硬币coins[i-1],两个选择加起来
if (j-coins[i-1] >= 0) {
dp[i][j] = dp[i - 1][j];
第i个硬币coins[i-1],用几次?
for (int k = 1; k * coins[i-1] <= j; k++) {
dp[i][j] += dp[i - 1][j - k*coins[i - 1]];
}
}
此时只能不用第i个硬币coins[i-1]
else
dp[i][j] = dp[i-1][j];
}
}
return dp[len][amount];
}
}