本次题目
- 518 零钱兑换II
- 377 组合总和IV
- 70 爬楼梯
- 322零钱兑换
- 279 完全平方数
- 139 单词拆分
完全背包
- 对比01背包,每件物品可以放入背包无限次。所以,遍历顺序先物品(由小到大)再背包(由小到大,同一物品可以放多次)。
518 零钱兑换II
- 完全背包DP(组合):
- 定义dp数组表示总金额为j的货币组合数为dp[j];
- 递推公式dp[j] += dp[j - coins[i]];
- 初始化dp[0] = 1;
- 遍历顺序(组合)先物品(由小到大)再背包(由小到大);
- 举例。
- 注意:若是排列则遍历顺序先背包(由小到大)再物品(由小到大)。
class Solution {
public int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
for(int i = 0; i < coins.length; i++){
for(int j = coins[i]; j <= amount; j++){
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
}
377 组合总和IV
- 完全背包DP(排列):
- 定义dp数组表示和为目标i的排列个数为dp[i];
- 递推公式dp += dp[i – nums[j]];
- 初始化dp[0] = 1;
- 遍历顺序(排列)先背包(由小到大)再物品(由小到大);
- 举例。
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target + 1];
dp[0] = 1;
for(int i = 0; i <= target; i++){
for(int j = 0; j < nums.length; j++){
if(i >= nums[j]) dp[i] += dp[i - nums[j]];
}
}
return dp[target];
}
}
70 爬楼梯
- 动态规划之前实现过,进阶为一步可以爬1到m个台阶。
- 完全背包DP:
- 定义dp数组dp[i]表示爬到第i个台阶有dp[i]种方法;
- 递推公式dp[i] += dp[i – j];
- 初始化dp[0] = 1;
- 遍历顺序(排列)先背包(由小到大)后物品(由小到大);
- 举例。
class Solution {
public int climbStairs(int n) {
int m = 2;
int[] dp = new int[n + 1];
dp[0] = 1;
for(int i = 0; i <= n; i++){
for(int j = 1; j <= m; j++){
if(i >= j) dp[i] += dp[i - j];
}
}
return dp[n];
}
}
322零钱兑换
- 对比上面的518 零钱兑换II。
- 完全背包DP:
- 定义dp数组dp[j]为总额为j的钱币最少个数为dp[j];
- 递推公式dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
- 初始化dp[0] = 0,其他值赋值为int最大;
- 遍历顺序(不强调组合或排序,讨论的是个数,因此先物品先背包都可以)先物品(由小到大)再背包(由小到大);
- 举例。
- 注意:最后结果为int最大时无结果,返回-1。
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
for(int i = 1; i < dp.length; i++){
dp[i] = Integer.MAX_VALUE;
}
for(int i = 0; i < coins.length; i++){
for(int j = coins[i]; j <= amount; j++){
if(dp[j - coins[i]] != Integer.MAX_VALUE) dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
if(dp[amount] == Integer.MAX_VALUE) return -1;
return dp[amount];
}
}
279 完全平方数
- 同上322零钱兑换。完全平方数为物品,目标值为背包容量。
- 完全背包DP:
- 定义dp[j]表示和为j的完全平方数最少数量为dp[j];
- 递推公式dp[j] = min(dp[j - i * i] + 1, dp[j]);
- 初始化dp[0] = 0,其他初始化为int最大;
- 遍历顺序先物品(由小到大)或背包(由小到大)都可以;
- 举例。
class Solution {
public int numSquares(int n) {
int[] dp = new int[n + 1];
for(int i = 1; i < dp.length; i++) dp[i] = Integer.MAX_VALUE;
for(int i = 1; i * i <= n; i++){
for(int j = i * i; j <= n; j++){
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
}
}
return dp[n];
}
}
139 单词拆分
- 对比使用回溯法的131 分割回文串。
- 完全背包DP:
- 定义dp数组dp[i]表示字符串长度为i时是否可以拆分为字典中的单词,true为可以,false为不可以;
- 递推公式if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true;
- 初始化dp[0]为true;
- 遍历顺序先物品(由小到大)或先背包(由小到大)都可以;
- 举例。
- 注意:切割字符串substring();判断列表是否有某元素contains();
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for(int i = 0; i <= s.length(); i++){
for(int j = 0; j <= i; j++){
if(wordDict.contains(s.substring(j, i)) && dp[j]) dp[i] = true;
}
}
return dp[s.length()];
}
}