背包问题模板

背包问题模板

模板参考地址:https://leetcode-cn.com/problems/combination-sum-iv/solution/xi-wang-yong-yi-chong-gui-lu-gao-ding-bei-bao-wen-/

背包问题具备的特征 : 给定一个target ( 数字或者字符串),再给定一个数组 nums(数字或字符串),问:能否使用nums中的元素做各种排列组合得到target。

背包问题模板:

  1. 如果是0-1背包,即数组中的元素不可重复使用。
for (int num: nums){
    for (int i=target;i>=num;i--){
        dp[i] += dp[i-num];
    }
}
  1. 如果是完全背包问题(数组中的元素可重复使用)并且不需要考虑元素之间的顺序。即 (1,2)和(2,1)看做是同一种组合
for (int num : nums){
    for (int i = num;i<= target;i++){
        dp[i]+=dp[i-num];
    }
}
  1. 如果组合问题需考虑元素之间的顺序,即(1,2)和(2,1)看做两种组合。
for (int i=1; i < target+1;i++){
    for (int num : nums){
        dp[i]+=dp[i-num];
    }
}   

下面通过几个例题来加深理解。

  1. 目标和

力扣原题地址:https://leetcode-cn.com/problems/target-sum/,这里对原题做了一些改变,以便于理解。

  • 题目描述
给定一个整数数组 nums 和一个整数 target。
在数组nums中选取若干元素,使得这些元素之和等于target,计算选取元素的方案数。
注:每个元素只能选取一次

每个元素只能使用一次,明显是0-1背包问题。套用上面的模板,很容易写出答案。详细过程可以参考力扣官方题解:https://leetcode-cn.com/problems/target-sum/solution/mu-biao-he-by-leetcode-solution-o0cp/

public int findTargetSumWays(int[] nums, int target) {
    //dp[i]的含义是 目标为i时的组合数,下同
    int[] dp = new int[target+1];
    //target为0时,只用一种方法,即什么也不选
    dp[0] = 1;
    for (int num:nums){
        //这里使用倒序的原因是因为每个元素只能使用一次
        for (int i=target;i>=num;i++){
            dp[i] += dp[i-num];
        }
    }
    return dp[target];
}
  1. 零钱兑换2

力扣原题地址: https://leetcode-cn.com/problems/coin-change-2/

  • 题目描述
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。 
请注意,顺序不同的序列被视作相同的组合。

这道题明显属于第二种情况,即完全背包问题且不考虑元素之间的顺序。

 public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[0] = 1;
        for (int coin : coins) {
            for (int i = coin; i <= amount; i++) {
                dp[i] += dp[i - coin];
            }
        }
        return dp[amount];
 }

上述做法不会重复计算不同的排列。因为外层循环是遍历数组 coins 的值,内层循环是遍历不同的金额之和,在计算 dp[i] 的值时,可以确保金额之和等于 i 的硬币面额的顺序,由于顺序确定,因此不会重复计算不同的排列

例如,coins=[1,2] ,对于 dp[3] 的计算,一定是先遍历硬币面额 1 后遍历硬币面额 2,只会出现以下 2 种组合:

3=1+1+1

3=1+2

硬币面额 2 不可能出现在硬币面额 1 之前,即不会重复计算 3=2+1 的情况。

详细过程可以参考力扣官方题解:https://leetcode-cn.com/problems/coin-change-2/solution/ling-qian-dui-huan-ii-by-leetcode-soluti-f7uh/

  1. 组合总和4

力扣原题:https://leetcode-cn.com/problems/combination-sum-iv/

  • 题目描述
给你一个由不同整数组成的数组 nums ,和一个目标整数 target 。
请你从 nums 中找出并返回总和为 target 的元素组合的个数。
请注意,顺序不同的序列被视作不同的组合。

这道题明显属于第三种情况,即完全背包问题且考虑元素之间的顺序。

public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target + 1];
        dp[0] = 1;
        for (int i = 1; i < target+1; i++) {
            for (int num : nums) {
                if (num<=i){
                    dp[i] += dp[i-num];
                }
            }
        }
        return dp[target];
}

上述做法是否考虑到选取元素的顺序?答案是肯定的。因为外层循环是遍历从 1 到 target 的值,内层循环是遍历数组 nums 的值,在计算 dp[i] 的值时,nums中的每个小于等于 i 的元素都可能作为元素之和等于 i 的排列的最后一个元素。

例如,1 和 3 都在数组 nums 中,计算 dp[4]的时候,排列的最后一个元素可以是 1 也可以是 3,因此 dp[1]dp[3]都会被考虑到,即不同的顺序都会被考虑到。

详细过程可以参考力扣官方题解:https://leetcode-cn.com/problems/combination-sum-iv/solution/zu-he-zong-he-iv-by-leetcode-solution-q8zv/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值