• 代码随想录第46天|139.单词拆分,了解多重背包,背包总结


    139.单词拆分

    动规五部曲

    1.确定valid数组以及下标的含义

    valid[i] : 字符串长度为i的话,valid[i]为true,表示可以拆分为一个或多个在字典中出现的单词

    2.valid初始化

    valid[0]一定要为true,否则递推下去后面都都是false了

    3.递推公式

    所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && valid[j]是true) 那么 valid[i] = true。

    4.遍历顺序

    关于求组合还是排列还是最小数做一个总结:

    求组合数动态规划:518.零钱兑换II (opens new window)

    求排列数:动态规划:377. 组合总和 Ⅳ (opens new window)动态规划:70. 爬楼梯进阶版(完全背包) (opens new window)

    求最小数:动态规划:322. 零钱兑换 (opens new window)动态规划:279.完全平方数

    而本题其实我们求的是排列数,为什么呢。 拿 s = "applepenapple", wordDict = ["apple", "pen"] 举例。

    "apple", "pen" 是物品,那么我们要求 物品的组合一定是 "apple" + "pen" + "apple" 才能组成 "applepenapple"。

    "apple" + "apple" + "pen" 或者 "pen" + "apple" + "apple" 是不可以的,那么我们就是强调物品之间顺序。

    所以说,本题一定是 先遍历 背包,再遍历物品。

    5.推导

    代码实现

    1. class Solution {
    2. public boolean wordBreak(String s, List<String> wordDict) {
    3. //完全背包
    4. //valid[i]表示字符串下标为i时,可以被字符串列表中的字符拼接出来
    5. HashSet<String> set=new HashSet<>(wordDict);//,将字典 wordDict 转化为一个 HashSet 集合 set
    6. boolean[] valid=new boolean[s.length()+1];//valid[i] 表示从字符串 s 的第 0 个字符到第 i 个字符(不包括第 i 个字符)所组成的子串是否可以被字典中的单词拆分。
    7. valid[0]=true;
    8. for(int i=1;i<=s.length();i++){
    9. for(int j=0;j<i&&!valid[i];j++){
    10. if(set.contains(s.substring(j,i))&&valid[j]){//i表示截取的结束位置,j表示截取的起始位置
    11. //对于每个子串,检查它是否存在字典中
    12. valid[i]=true;//表示从0到i的子串可以被拆分
    13. }
    14. }
    15. }
    16. return valid[s.length()];//表示整个字符串s是否可以被拆分成字典中的单词
    17. }
    18. }

    多重背包

    有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。

    其实多重背包和01背包很想,01背包每个物品只有1个,多重背包只是每个物品有多个

    1. ublic void testMultiPack1(){
    2. // 版本一:改变物品数量为01背包格式
    3. List<Integer> weight = new ArrayList<>(Arrays.asList(1, 3, 4));
    4. List<Integer> value = new ArrayList<>(Arrays.asList(15, 20, 30));
    5. List<Integer> nums = new ArrayList<>(Arrays.asList(2, 3, 2));
    6. int bagWeight = 10;
    7. for (int i = 0; i < nums.size(); i++) {
    8. while (nums.get(i) > 1) { // 把物品展开为i
    9. weight.add(weight.get(i));
    10. value.add(value.get(i));
    11. nums.set(i, nums.get(i) - 1);
    12. }
    13. }
    14. int[] dp = new int[bagWeight + 1];
    15. for(int i = 0; i < weight.size(); i++) { // 遍历物品
    16. for(int j = bagWeight; j >= weight.get(i); j--) { // 遍历背包容量
    17. dp[j] = Math.max(dp[j], dp[j - weight.get(i)] + value.get(i));
    18. }
    19. System.out.println(Arrays.toString(dp));
    20. }
    21. }

    背包问题总结

    核心五步

    1. 确定dp数组(dp table)以及下标的含义
    2. 确定递推公式
    3. dp数组如何初始化
    4. 确定遍历顺序
    5. 举例推导dp数组

    01背包

    01背包的一维和二维两种实现方式的差异体现在:

    一维:倒序遍历(如果正序遍历背包的话一个物品会被放置多次),且必须先遍历物品再遍历背包(防止每个背包只放入一个物品)

    二维:正序遍历,先遍历背包还是先遍历物品都可以

    416. 分割等和子集:

    01背包问题,先遍历物品再倒序遍历背包,要判断给定的数组能不能被分割成和相等的两组,首先对整个数组求和sum,然后得到target=sum/2,确定dp[i]表示容量为i背包的最大价值,确定递推公式为dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]);

    1049. 最后一块石头的重量 II 

    01背包问题,跟416 分割等和子集很像,先遍历物品再倒序遍历背包

    494.目标和

    01背包问题,先遍历物品再倒序遍历背包,首先公式推导得出left=(sum+target)/2,问题转换成在集合nums中找出和为left的组合。求装满背包有几种方法的情况下,递推公式一般为:dp[j]+=dp[j-nums[i]];

    474.一和零

    找出并返回 strs 的最大子集的长度,找出的该子集中最多有m个0和n个1,dp[i][j]表示i个0和j个1时的最大子集大小,首先要遍历字符串数组的每个字符串,统计每个字符串的0的个数和1的个数,然后倒序遍历zeroNum和倒序遍历oneNum

    完全背包

    完全背包相较于01背包就是物品可以取无限次

    377. 组合总和 Ⅳ

    这道题是排列问题不是组合,dp[i]: 凑成目标正整数为i的排列个数为dp[i],遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历,在遍历过程中需要判断容量j是否大于等于nums[i],只有满足这个条件才可以执行递推公式

    518.零钱兑换II

    求的是组合数,dp[i]表示凑成总金额为i的货币组合数,要判断coins[i]与我们当前遍历的背包的容量j的大小关系,现遍历背包还是先遍历物品都可以

    有一个结论:求组合数先遍历物品再遍历背包;求排列数先遍历背包再遍历物品

    70. 爬楼梯

    之前没学背包问题前用普通动规分析就可以做,现在用完全背包的思路,dp[i]表示到达第i阶的方法数,要判断i与我们当前遍历的背包的容量j的大小关系,dp[j]+=dp[j-i];

    322. 零钱兑换

    dp[j]:凑足金额为j所需钱币的最少个数,这里也是求的组合问题。下标为0初始化为0,非0下标初始化为最大值,需要进行判断防止int类型溢出:

      if (dp[j - coins[i]] != Integer.MAX_VALUE) {

                 //如果是dp[j - coins[i]等于Integer.MAX_VALUE,那么+1后溢出,变成-2147483648
                        dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);

    }

    先遍历背包还是物品无所谓,求的是组合数的最小数,所以不影响,如果要求我们把所有组合情况列出来,那么我们就需要回溯法了

    279.完全平方数

    这道题和322. 零钱兑换思路基本一致,也是求组合数的最小数,dp[i]表示组成和为i的最少完全平方数个数,递推公式:dp[j]=Math.min(dp[j],dp[j-i*i]+1);先遍历背包还是物品无所谓

    多重背包 

    了解即可,因为多重背包问题可以被拆成01背包看

     

  • 相关阅读:
    TCP的连接套接口哈希表初始化
    Android Rxjava架构原理与使用的详解解答
    Toou-2D windows 打包部署
    CMD脚本实战教程
    .NET 不受 美国出口管理条例(EAR) 的约束
    基于自动化设备和现代化仓储精益管理思想开发的仓库管理系统
    【国产32位mcu】电动车控制芯片CS32F031C8T6的应用
    BitSet源码解析,位运算玩的真六
    【C++】动态内存管理 ③ ( C++ 对象的动态创建和释放 | new 运算符 为类对象 分配内存 | delete 运算符 释放对象内存 )
    docker使用nginx
  • 原文地址:https://blog.csdn.net/m0_67042480/article/details/132789333