代码随想录算法训练营 Day 46 | 139.单词拆分,关于多重背包,你该了解这些!,背包问题总结篇!

139.单词拆分

讲解链接:代码随想录-139.单词拆分

  1. 确定 dp 数组以及下标的含义:dp[i] : 字符串长度为 i 的话,dp[i]为 true,表示可以拆分为一个或多个在字典中出现的单词。

  2. 确定递推公式:如果确定 dp[j] 是 true,且 [j, i] 这个区间的子串出现在字典里,那么 dp[i]一定是 true。(j < i )。
    所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是 true) 那么 dp[i] = true。

  3. dp 数组如何初始化:从递推公式中可以看出,dp[i] 的状态依靠 dp[j]是否为 true,那么 dp[0]就是递推的根基,dp[0]一定要为 true,否则递推下去后面都都是 false 了。

  4. 确定遍历顺序:题目中说是拆分为一个或多个在字典中出现的单词,所以这是完全背包。还要讨论两层 for 循环的前后顺序。

    1. 如果求组合数就是外层 for 循环遍历物品,内层 for 遍历背包。
    2. 如果求排列数就是外层 for 遍历背包,内层 for 循环遍历物品。
    3. 而本题其实我们求的是排列数,为什么呢。 拿 s = "applepenapple", wordDict = ["apple", "pen"] 举例。
      "apple", "pen" 是物品,那么我们要求 物品的组合一定是 "apple" + "pen" + "apple" 才能组成 "applepenapple"。
      "apple" + "apple" + "pen" 或者 "pen" + "apple" + "apple" 是不可以的,那么我们就是强调物品之间顺序。
      所以说,本题一定是 先遍历 背包,再遍历物品。
public boolean wordBreak(String s, List 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 weight = new ArrayList<>(Arrays.asList(1, 3, 4));
    List value = new ArrayList<>(Arrays.asList(15, 20, 30));
    List 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.完全平方数

对于背包问题,其实递推公式算是容易的,难是难在遍历顺序上,如果把遍历顺序搞透,才算是真正理解了。

总结

​​

 

你可能感兴趣的:(算法,java,动态规划)