算法笔记:背包问题(下)

Posted 明天会更好new

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法笔记:背包问题(下)相关的知识,希望对你有一定的参考价值。

算法笔记:背包问题(下)

前言

终于独立做出来背包的题了,之前的总结非常有效,这篇文章就是先做道每日一题回顾一下,然后把上篇文章后面留的两道题做一下。

做背包问题的思路(模板)

还是先回顾(总结)一下,背包算法,就是一堆数要凑成一个target,然后转换成普通dp——》都多少种凑法、能不能凑成等。

核心代码如下:

for(int c:coins)
            for(int i=c;i<=amount;i++)
                //如果是背包里的物品有限就倒序,防重复,无限就正序
                //dp[i]+=dp[i-c];递推公式,根据具体题目确定
            
        

518. 零钱兑换 II

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 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]为例:

012345
1111111
22233
54

找递推公式这是一个找规律的过程,没有说背过一个规律所有题都适用,只不过在背包算法里面,绝大部分都是和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,但是还是不大行,算了吧,继续中等题吧。

416. 分割等和子集

给你一个 只包含正整数 的 非空 数组 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]为例

01234567891011
1110000000000
51100000
111
52200012

494. 目标和

给你一个整数数组 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了,好了下一篇再见了。

以上是关于算法笔记:背包问题(下)的主要内容,如果未能解决你的问题,请参考以下文章

学习数据结构笔记(17) --- [动态规划(由背包问题引入)]

0/1背包-递归算法

算法随笔一(背包问题)

背包问题,贪心算法实现

图解算法-怎么用动态规划解决0-1背包问题

算法面试题——动态规划01背包问题