round1/day19/完全背包02

1. 关于动规

  1. 01背包
  2. 完全背包
    • 正序遍历背包 for (int j=target; j>=weight[i]; j++)
    • 先背包or先物品?
      • 先物品后背包:适用于求组合
      • 先背包后物品:适用于求排列(不去重组合)
  3. 联动排列组合专题:(待施工)
    • 算排列/组合数量:动规377 518;
    • 列出所有排列/组合方法:回溯39 40

2. 例题

lc377 组合总和Ⅳ

思路

  1. 组合vs 排列:

    • 组合:先遍历物品i=0并递增,故一定先放物品1,再放物品2,如[1,2],不会出现[2,1]
    • 排列:先遍历背包i=0并递增,则在背包容量固定的情况下,计算所有的排列方法,既有[1,2]也有[2,1]
      • 注意:此时的遍历起始顺序,背包和物品都从0开始,但是在执行正文里需要判定背包容量是否可以容纳物品重量if(j>=weight[i])
  2. 将问题转化为背包问题:

    • step1,
      • 要素察觉无限数字,满足固定总和=>完全背包
    • step2,
      • 排除情况n/a
    • step3,
      • 背包概念化
      • 背包:总和target
      • 物品:数字们nums。物品重量nums[i],物品价值nums[i],物品特性可以无限重复使用
      • 完全背包情况2:用无限用品完全装满背包有几种排列方法(先遍历背包,再遍历物品
      • 对应公式:dp[i]+=dp[i-nums[j]]
  3. 正式代码:

    • 创建dp:dp[i],当要求总和为i时,可以组合的方法数量为dp[i]
    • 初始化:dp[0]=1
    • 递推公式:dp[i]+=dp[i-nums[j]]
    • 遍历顺序:不去重的所有组合(排列问题),故先背包再物品,全都顺序遍历

易错点

注意不去重:顺序不同的序列可以重复组合

代码实现

class Solution {
    public int combinationSum4(int[] nums, int target) {
        //创建dp数组
        int[] dp = new int[target+1];
        //初始化
        dp[0]=1;
        //递推公式
        for(int i=0;i<=target;i++){ //背包容量
            for(int j=0;j<nums.length;j++){ //物品:这里只能从物品0开始遍历,不能像再遍历背包时那样从nums[i]开始数,故判定条件if要加在里面
                if(i>=nums[j]) 
                    dp[i]+=dp[i-nums[j]]; //注意因为要确保i-nums[j]有意义,故要加if(i>=nums[j])的判定
                    
            }
            System.out.println(dp[i]);
        }
        return dp[target];
    }
}

**

lc322 零钱兑换

思路

  1. 将问题转化为背包问题:
    背包概念化
    - 背包容量:amount总金额
    - 物品:硬币们coins。物品价值/重量coins[i],物品特性无限使用+组合需去重,物品数量dp[j]
    - 完全背包情况3:完全装满背包时,物品的最小数量 Math.min(dp[j],dp[j-coins[i]]+1)

  2. 正式代码:

    • dp数组:dp[j]。当总金额为j时,完全满足总金额j所需要的最小物品数量
    • 初始化:先预设最大,然后用min慢慢削小 Arrays.fill(dp,n+1)+ dp[0]=0
    • 排除例外:如果凑不到,则return -1
    • 递推公式:Math.min(dp[j],dp[j-coins[i]]+1)
    • 遍历顺序:先背包,再物品,**注意背包容量不足物品重量的数组边界问题

前置知识

填充数组中的所有值:Arrays.fill(nums,?)

易错点

因为是求min,所以dp的初始值必须最大化,而不能只用dp[0]=0

代码实现

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount+1];
        
        //例外:amount=0
        if(amount==0) return 0;
        
        //初始化
        //易错:因为是求min,所以dp的初始值必须最大化,而不能只用dp[0]=0
        Arrays.fill(dp,amount+1);
        dp[0]=0;



        for(int i=0;i<coins.length;i++){ //物品
            for(int j=coins[i];j<=amount;j++){ //背包
                dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
            }
        }

        //排除例外:凑不到(dp[]仍然是初始值amount+1)要return-1
        
        if(dp[amount]>amount){
            return -1;
        }else{
            return dp[amount];
        }
    }
}

**

lc279 完全平方数

思路

遍历n里的所有数字,挑出完全平方数数组nums
遍历nums,完全背包情况3:完全背包-完全装满-求物品最小数量

前置知识

链表转为数组:链表.stream().mapToInt(Integer::intValue).toArray();

待优化

类似322,多了一个提取完全平方数的步骤
注意有一段代码写完的高效版本,待优化

代码实现

class Solution {
    public int numSquares(int n) {
        int[] nums = perfectSquare(n);

        int[] dp=new int[n+1];
        //初始化:先预设最大,然后慢慢削小
        Arrays.fill(dp,n+1);
        dp[0]=0;

        //递推
        for(int i=0;i<nums.length;i++){ //物品
            for(int j=nums[i];j<=n;j++){ //背包
                dp[j]=Math.min(dp[j],dp[j-nums[i]]+1);
            }
        }

        return dp[n]; 

    }

    public int[] perfectSquare(int n){
     LinkedList<Integer> perfectsquare = new LinkedList<>();
        
        for(int i=1;i<=n;i++){
            double squareroot = Math.sqrt(i);
            if(squareroot == (int)squareroot){
                perfectsquare.add(i);
            }
        }
        return perfectsquare.stream().mapToInt(Integer::intValue).toArray();
    }
}

**

lc139 单词拆分

思路

基于原始字符串,比较s-correctWord的长度里,是否跟word匹配,而不用额外创建字符串链表并add(word in wordDict),更加高效

  1. 将问题转化为背包问题:
    背包概念化
    - 背包:字符串
    - 物品:单词们wordDict,物品价值单词是否和s中对应字段匹配,物品重量单词长度,物品特性每个物品可重复使用且组合可重复——排列
    - 完全背包情况3-boolean:求排列情况并返回这种排列是否可行
    - 对应递推公式:dp[i-b’s_Length]==true && a’s_slice.equals(b)

  2. 正式代码:

    • dp数组:当背包容量为i/字符串长度为i时,dp[i]表示是否能够凑成单词s(boolean)
    • 初始化:dp[0]=true
    • 递推公示:排列方法:s.substring(i-j.length()).equals(word)
    • 遍历顺序:先背包,再物品

前置知识

  1. 看字符串中某一段是否与单词匹配:substring。s.substring(i-j.length()).equals(word)
  2. 善用for(string num: nums)

代码实现

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        boolean[] dp=new boolean[wordDict.size()];
        //初始化
        dp[0]=true;

        //递推
        //遍历背包(字段)本身,一旦有符合true的,就可以略过这段不提
        for(int i=1;i<s.length();i++){
            //遍历物品,看这个单词是否可以跟背包字符串中的特定字段匹配
            for(String word:wordDict){
                int len=word.length();
                if(i>=len && dp[i-len]==true && s.substring(i-len,i).equals(word)){ //先从s的字符串中留出当前word的长度,然后把word填进去看看是否符合
                    return true; 
                    break;
                }
            }
        }
        return false;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值