算法笔记:动态规划——背包问题(上)

Posted 明天会更好new

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但是压缩空间就成上面的代码了。

01234567891011
22[0]2[1]2[2]2[3]2[4]2[5]2[6]2[7]2[8]2[9]
77[0]7[1]9[2]9[3]9[4]
44[0]4[1]6[2]7[3]7[4]9[5]9[6]11[7]
11[0]2[1]3[2]4[3]5[4]6[5]7[6]8[7]9[8]10[9]11[10]
88[0]9[1]10[2]11[3]
11[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为例,倒序成什么了:

01234567891011
111(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。

01234567891011
11234567891011
212[1]2[2]3[3]344556
5122[2]332[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

01234
201[0]2[1]
301[0]

其实这道题我认为难点是条件判断。

练习

先这样了,练习一下,明天再继续。

416. 分割等和子集

494. 目标和

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

动态规划第六篇:01背包问题(分割等和子集 + 最后一块石头的重量 II)

动态规划第六篇:01背包问题(分割等和子集 + 最后一块石头的重量 II)

LeetCode 1049 背包动态规划

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

LeetCode 1049 最后一块石头的重量[动态规划] HERODING的LeetCode之路

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