代码随想录第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.推导

代码实现

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

    }
}

多重背包

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

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

ublic 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));
    }
}

背包问题总结

核心五步

  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背包看

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值