139.单词拆分
讲解链接:代码随想录-139.单词拆分
-
确定 dp 数组以及下标的含义:dp[i] : 字符串长度为 i 的话,dp[i]为 true,表示可以拆分为一个或多个在字典中出现的单词。
-
确定递推公式:如果确定 dp[j] 是 true,且 [j, i] 这个区间的子串出现在字典里,那么 dp[i]一定是 true。(j < i )。
所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是 true) 那么 dp[i] = true。 -
dp 数组如何初始化:从递推公式中可以看出,dp[i] 的状态依靠 dp[j]是否为 true,那么 dp[0]就是递推的根基,dp[0]一定要为 true,否则递推下去后面都都是 false 了。
-
确定遍历顺序:题目中说是拆分为一个或多个在字典中出现的单词,所以这是完全背包。还要讨论两层 for 循环的前后顺序。
- 如果求组合数就是外层 for 循环遍历物品,内层 for 遍历背包。
- 如果求排列数就是外层 for 遍历背包,内层 for 循环遍历物品。
- 而本题其实我们求的是排列数,为什么呢。 拿 s = "applepenapple", wordDict = ["apple", "pen"] 举例。
"apple", "pen" 是物品,那么我们要求 物品的组合一定是 "apple" + "pen" + "apple" 才能组成 "applepenapple"。
"apple" + "apple" + "pen" 或者 "pen" + "apple" + "apple" 是不可以的,那么我们就是强调物品之间顺序。
所以说,本题一定是 先遍历 背包,再遍历物品。
public boolean wordBreak(String s, List<String> wordDict) {
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i < dp.length; i++) {
for (int j = 0; j < wordDict.size(); j++) {
String str = wordDict.get(j);
Integer len = str.length();
if (i >= len && dp[i - len] && str.equals(s.substring(i - len, i))) {
dp[i] = true;
break;
}
}
}
return dp[dp.length - 1];
}
关于多重背包,你该了解这些!
讲解链接:代码随想录-关于多重背包,你该了解这些!
版本二:改变遍历个数
public void testMultiPack2() {
// 版本二:改变遍历个数
int[] weight = new int[]{1, 3, 4};
int[] value = new int[]{15, 20, 30};
int[] nums = new int[]{2, 3, 2};
int bagWeight = 10;
int[] dp = new int[bagWeight + 1];
for (int i = 0; i < weight.length; i++) { // 遍历物品
for (int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
// 以上为01背包,然后加一个遍历个数
for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++) { // 遍历个数
dp[j] = Math.max(dp[j], dp[j - k * weight[i]] + k * value[i]);
}
System.out.println(Arrays.toString(dp));
}
}
}
版本一:改变物品数量为 01 背包格式
public void testMultiPack1() {
// 版本一:改变物品数量为01背包格式
List<Integer> weight = new ArrayList<>(Arrays.asList(1, 3, 4));
List<Integer> value = new ArrayList<>(Arrays.asList(15, 20, 30));
List<Integer> nums = new ArrayList<>(Arrays.asList(2, 3, 2));
int bagWeight = 10;
for (int i = 0; i < nums.size(); i++) {
while (nums.get(i) > 1) { // 把物品展开为i
weight.add(weight.get(i));
value.add(value.get(i));
nums.set(i, nums.get(i) - 1);
}
}
int[] dp = new int[bagWeight + 1];
for (int i = 0; i < weight.size(); i++) { // 遍历物品
for (int j = bagWeight; j >= weight.get(i); j--) { // 遍历背包容量
dp[j] = Math.max(dp[j], dp[j - weight.get(i)] + value.get(i));
}
System.out.println(Arrays.toString(dp));
}
}
背包问题总结篇!
讲解链接:代码随想录-背包问题总结篇!
01 背包
在动态规划:关于 01 背包问题,你该了解这些! 中我们讲解二维 dp 数组 01 背包先遍历物品还是先遍历背包都是可以的,且第二层 for 循环是从小到大遍历。
和动态规划:关于 01 背包问题,你该了解这些!(滚动数组) 中,我们讲解一维 dp 数组 01 背包只能先遍历物品再遍历背包容量,且第二层 for 循环是从大到小遍历。
一维 dp 数组的背包在遍历顺序上和二维 dp 数组实现的 01 背包其实是有很大差异的,大家需要注意!
完全背包
说完 01 背包,再看看完全背包。
在动态规划:关于完全背包,你该了解这些! (opens new window)中,讲解了纯完全背包的一维 dp 数组实现,先遍历物品还是先遍历背包都是可以的,且第二层 for 循环是从小到大遍历。
但是仅仅是纯完全背包的遍历顺序是这样的,题目稍有变化,两个 for 循环的先后顺序就不一样了。
如果求组合数就是外层 for 循环遍历物品,内层 for 遍历背包。
如果求排列数就是外层 for 遍历背包,内层 for 循环遍历物品。
相关题目如下:
- 求组合数:动态规划:518.零钱兑换 II
- 求排列数:动态规划:70. 爬楼梯进阶版(完全背包)
如果求最小数,那么两层 for 循环的先后顺序就无所谓了,相关题目如下:
- 求最小数:动态规划:322. 零钱兑换 、动态规划:279.完全平方数
对于背包问题,其实递推公式算是容易的,难是难在遍历顺序上,如果把遍历顺序搞透,才算是真正理解了。
总结