终于独立做出来背包的题了,之前的总结非常有效,这篇文章就是先做道每日一题回顾一下,然后把上篇文章后面留的两道题做一下。
还是先回顾(总结)一下,背包算法,就是一堆数要凑成一个target,然后转换成普通dp——》都多少种凑法、能不能凑成等。
核心代码如下:
for(int c:coins){
for(int i=c;i<=amount;i++){
//如果是背包里的物品有限就倒序,防重复,无限就正序
//dp[i]+=dp[i-c];递推公式,根据具体题目确定
}
}
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 1:
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change-2
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
之前说了,如果告诉你target明摆着是背包算法,那就是条件难或者递推公式难,但是这题是真简单,就告诉你了是背包算法,还没有条件。
public int change(int amount, int[] coins) {
int[] dp=new int[amount+1];
dp[0]=1;
for(int c:coins){
for(int i=c;i<=amount;i++){
//如果是背包里的物品有限就倒序,防重复,无限就正序
dp[i]+=dp[i-c];
}
}
return dp[amount];
}
这道题是正序遍历,因为物品是无限的,就是要让它重复,倒序是为了让物品不重复,这个大家画一画就知道该倒序还是正序了,我这个结论也不一定对,大家还是会了之后,根据题意以自己判断为主,别有的题无限然后也倒序,我这个误导了大家
。
具体过程,以amount = 5, coins = [1, 2, 5]为例:
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1 | 1 | 1 |
2 | 2 | 2 | 3 | 3 | ||
5 | 4 |
找递推公式这是一个找规律的过程,没有说背过一个规律所有题都适用,只不过在背包算法里面,绝大部分都是和dp[i-c]有关系
,记住这个之后可以帮我们缩小找规律的难度。
先说的dp[i]是什么意思用目前所有的币种来凑我有多少种凑法,看第一行0,dp[0]=1,因为0不管用谁凑都只有一种凑法就是没有,没有是1种凑法。
1用1凑就1种方法,2用1凑,只能是1+1,同理3,、4、5,这里还没什么规律!看第二行,直接从2开始,因为0用2还是用1凑时候的方法没有变,1同理,其实这里就出规律了:
for(int i=c;i<=amount;i++)
继续从2开始,2用2凑是1种,2用1凑是1种这个在上一次遍历的时候已经知道了
,总共是2种方法;3用2自己凑不出来,必须借助1来凑,借助1是在dp[1]的基础上,这里其实就清楚的看出规律了!!!3可以用1凑,但这是上一次遍历就知道的1种方法,用1和2一起凑1+2,是在dp[1]的基础上,因为1和1+2这是同一种方法
,就是这样,递推公式就出来了。
上面是完整的背包还有dp递推公式怎么来的,实在不会就记住是和dp[i-c]有关系然后在看是±*/还是+1啥的,最终代码:
for(int c:coins){
for(int i=c;i<=amount;i++){
//如果是背包里的物品有限就倒序,防重复,无限就正序
dp[i]+=dp[i-c];
}
}
还不会就在纸上画个表格。
其实是想挑战一下困难题879,但是还是不大行,算了吧,继续中等题吧。
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
public boolean canPartition(int[] nums) {
int sum=0;
for(int num:nums){
sum+=num;
}
if(sum%2!=0){
return false;
}
int target=sum/2;
int[] dp=new int[target+1];
dp[0]=1;
for(int num:nums){
for(int i=target;i>=num;i--){
dp[i]+=dp[i-num];
}
}
return dp[target]!=0;
}
以nums = [1,5,11,5]为例
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
5 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | |||||
11 | 1 | |||||||||||
5 | 2 | 2 | 0 | 0 | 0 | 1 | 2 |
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/target-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
public int findTargetSumWays(int[] nums, int target) {
int sum=0;
for(int num:nums){
sum+=num;
}
if(sum<target||(sum+target)%2!=0){
return 0;
}
int targetx=(sum+target)/2;
int[] dp=new int[targetx+1];
dp[0]=1;
for(int num:nums){
for(int i=targetx;i>=num;i--){
dp[i]+=dp[i-num];
}
}
return dp[targetx];
}
这道题就是需要转换一下:x-(sum-x)=target——》x=(sum+target)/2,从凑target有多少种方法到凑x有多少种方法。然后和416完全一样。
好了dp背包就到这里了,会了之后就很简单,但是困难题还是很难,总结一下步骤:找到target与物品数组——》确定遍历顺序——》dp递推公式,就ok了,好了下一篇再见了。