算法笔记:动态规划——背包问题(上)
Posted liu++
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法笔记:动态规划——背包问题(上)相关的知识,希望对你有一定的参考价值。
算法笔记:动态规划——背包问题
前言
今天是6.8,连续几天的每日一题都是背包问题我受不了了,必须解决了它,说实话有一些复杂,但是有不是完全复杂,这篇文章打算用几个例题来尽量解决掉背包算法。
什么是背包问题
直接看一道题:
1049. 最后一块石头的重量 II
有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。示例 1:
输入:stones = [2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/last-stone-weight-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
定义
背包问题就是规定了一个target(背包总体积)
,然后给你一堆石头,每个石头有自己的体积vi,问你多少块石头
可以填满背包——》vi*n=target,求n。
ok知道定义了,可是这和上面的题也没关系了,上面的题既没有target(总量)也没用问多少个,问题和条件都对不上啊!确实是没有直接告诉用背包算法,这是这道题的难点——》需要转化,但是转化之后非常好做,背包算法的题乃至dp的题都是出题非常活,有的是告诉你就是用dp,但是程序需要添加复杂的条件判断,有的就是上面这种,看不出来用dp需要转换。
题解
说正经的。题目问的是剩下的石头但是剩下的石头是哪来的?——》两堆石头的差
!要求是剩下的石头尽量小——》两堆石头质量尽量相等
。
所以:x+y=sum,x≈y,|x-y|=res。现在sum知道,要求res,需要知道x和y,x和y尽量相等=sum/2,所以就是从一大堆里面挑出一堆石头≈sum/2——》target,知道一堆了另一堆就是sum-x。两堆一减就出来结果了。
好了分析完了直接写代码!
public int lastStoneWeightII(int[] stones) {
int sum=0;
for(int s:stones){
sum+=s;
}
int target=sum/2;
int[] dp=new int[target+1];
for(int s:stones){
for(int i=target;i>=s;i--){
dp[i]=Math.max(dp[i],dp[i-s]+s);
}
}
return sum-2*dp[target];//相当于y-x,因为y-x=sum-x-x,x==dp[target],上面代码的结果dp[target]一定是小的那份
}
好了,有代码了现在解释为什么这么写!就以[2,7,4,1,8,1]为例,只说核心代码,上面的求target相信大家已经明白了,就是明确背包的容量(有的题会给出target,给出的题一般是找石头比较难),下面的代码是找一堆石头去填背包,那么找哪些呢?根据dp的递推公式。
for(int s:stones){
for(int i=target;i>=s;i--){
dp[i]=Math.max(dp[i],dp[i-s]+s);
}
}
先说dp[i]
是什么含义!——》重量在0~i之间选的石头的质量总和
!dp[i-s]是什么意思?——》质量0~i-s之间选的石头质量总和,为什么要弄dp[i-s]——》dp[i-s]+s是接近dp[i]的质量!s是变化的,我们要让dp[target]尽量大所以Math.max(dp[i],dp[i-s]+s)
。具体过程如下,实际上是二维dp但是压缩空间就成上面的代码了。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2 | 2[0] | 2[1] | 2[2] | 2[3] | 2[4] | 2[5] | 2[6] | 2[7] | 2[8] | 2[9] | ||
7 | 7[0] | 7[1] | 9[2] | 9[3] | 9[4] | |||||||
4 | 4[0] | 4[1] | 6[2] | 7[3] | 7[4] | 9[5] | 9[6] | 11[7] | ||||
1 | 1[0] | 2[1] | 3[2] | 4[3] | 5[4] | 6[5] | 7[6] | 8[7] | 9[8] | 10[9] | 11[10] | |
8 | 8[0] | 9[1] | 10[2] | 11[3] | ||||||||
1 | 1[0] | 2[1] | 3[2] | 4[3] | 5[4] | 6[5] | 7[6] | 8[7] | 9[8] | 10[9] | 11[10] |
一维就是二维叠加在一起,二维的代码不好写,直接理解了一维的就ok!
背包问题多样题型
实际上是万变不离其宗,但是往往只是稍微价个条件换个问题就不会了。
322. 零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
如果是第一次接触背包问题,看完了上面的分析,现在看这个题目知道要用背包算法那就不错了,我上面写的也没白费。
这个就是target给出来了,但是现在不是每种石头只能放一个了!
,每种石头只能放一个那是简单题(并不是),现在说可以无数个,让你数最少需要几个,不管是什么样的石头。
我们直接画表分析,做算法题必须在纸上画尤其是dp这种,不然做出来也是蒙的或者背的模板。
这里提一下,大家注意到上面那道题遍历的顺序了吗,第二个循环是倒着的,从大到小。为什么要这样?——》因为每个石头只有一个,你正着遍历试试,就重复了啊!
for(int s:stones){
for(int i=target;i>=s;i--){
dp[i]=Math.max(dp[i],dp[i-s]+s);
}
}
那么这道题呢,现在石头无数个,你随便放,你再试试倒序,根本不行,以coins = [1, 2, 5], amount = 11为例,倒序成什么了:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 1(2) | 1(3) | 1(4) | 1(5) | 1(6) | 1(7) | 1(8) | 1(9) | 1(10) | 1(11) |
括号里面是正序应该的数字,我们都知道11是11个1元,倒序成了1了那肯定是不行的,所以必须正序,顺理成章就是11的时候是11。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
2 | 1 | 2[1] | 2[2] | 3[3] | 3 | 4 | 4 | 5 | 5 | 6 | ||
5 | 1 | 2 | 2[2] | 3 | 3 | 2[5] | 3[6] |
括号里面的下标,是说这个数字是根据哪个下标数字获得的,有的我想不清楚了就标上了,有的没标。
可以找到规律吗?根据上一道题。递推公式——》Math.min(dp[j],dp[j-coins[i]]+1)。
直接看代码:
public int coinChange(int[] coins, int amount) {
if(coins.length == 0)
return -1;
int n=coins.length;
int[] dp=new int[amount+1];
Arrays.fill(dp,1,dp.length,Integer.MAX_VALUE);//
for(int c:coins){
for(int i=c;i<=amount;i++){
if(dp[i-c] != Integer.MAX_VALUE){//
dp[i]=Math.min(dp[i],dp[i-c]+1);
}
}
}
if(dp[amount] != Integer.MAX_VALUE)//
return dp[amount];
return -1;
}
整体思路上1049一样,但是可以看到多了几行条件代码,因为如果dp[i]是MAX_VALUE说明用上一种硬币凑不出这个金额,比如用[2,3],4
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
2 | 0 | 1[0] | 2[1] | ||
3 | 0 | 1[0] |
其实这道题我认为难点是条件判断。
练习
先这样了,练习一下,明天再继续。
416. 分割等和子集
494. 目标和
以上是关于算法笔记:动态规划——背包问题(上)的主要内容,如果未能解决你的问题,请参考以下文章
014-背包问题-动态规划-《算法设计技巧与分析》M.H.A学习笔记
学习数据结构笔记(17) --- [动态规划(由背包问题引入)]