背包问题模板
背包问题具备的特征 : 给定一个target ( 数字或者字符串),再给定一个数组 nums(数字或字符串),问:能否使用nums中的元素做各种排列组合得到target。
背包问题模板:
- 如果是0-1背包,即数组中的元素不可重复使用。
for (int num: nums){
for (int i=target;i>=num;i--){
dp[i] += dp[i-num];
}
}
- 如果是完全背包问题(数组中的元素可重复使用)并且不需要考虑元素之间的顺序。即 (1,2)和(2,1)看做是同一种组合
for (int num : nums){
for (int i = num;i<= target;i++){
dp[i]+=dp[i-num];
}
}
- 如果组合问题需考虑元素之间的顺序,即(1,2)和(2,1)看做两种组合。
for (int i=1; i < target+1;i++){
for (int num : nums){
dp[i]+=dp[i-num];
}
}
下面通过几个例题来加深理解。
- 目标和
力扣原题地址: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];
}
- 零钱兑换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/
- 组合总和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/