【算法】动态规划(二)——从根本解决完全背包与01背包

目录

一、前言

二、完全背包

• 什么是完全背包?

(1)518. 零钱兑换 II

• 整体代码

 (2)322. 零钱兑换

• 整体代码

(3)377. 组合总和 Ⅳ

•整体代码

01背包与完全背包及解题总结💐💐💐


一、前言

感谢你能点开这篇文章,并与我们一起学习!

本篇文章继上篇动态规划(一),由01背包往完全背包深入了解。


二、完全背包

• 什么是完全背包?

完全背包,是一种经典的信息学问题,是研究一个固定容量的背包内能装多大价值的东西的问题。

跟01背包区别的是:完全背包可以重复的选择同一样物品

这时我们需要考虑的是:物品体积?物品价值?背包体积?对于选择几次不做考虑,遍历背包是会对一个物品多次选择;

(1)518. 零钱兑换 II

leetcode传送➡️https://leetcode.cn/problems/coin-change-ii/

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。 

题目数据保证结果符合 32 位带符号整数。

示例 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

1. dp数组定义

本题与动态规划(一)中的“目标和”题目思路是一样的,但是这里的物品可以选择多次,所以属于完全背包问题。

题目出现总金额,我们不难想到将总金额作为dp背包的体积,又因为这道题要求我们求满足条件的方法个数,所以dp背包的含义为:装满背包的方法个数;

2. 递推公式 

装满该背包体积的物品,由减去该物品体积的另一被装满的背包加上该物品体积所得;同时我们时刻注意dp的含义——方法个数,及推导出,递推公式为:

dp[j] += dp[j - coins[i]];

这里可能有人会对后面+1,但是仔细想,一个装满的背包加上一个物品来到另一体积更大的背包,方法数其实还是装满之前体积较小的背包的方法数。 

3. 初始化

dp[0]表示体积为0的背包装满的方法数,考虑到递推到 dp[0] 时是直接加上该背包的方法数的,所以我们需要对dp[0]初始化为1

举例:体积为 5 的物品由 dp[0] 加到 dp[5]

       dp[5] += dp[5 - 5];

(如果dp[0]初始化为0的话,通过递推后的答案依然是0 )

4. 遍历顺序

由于是完全背包,一个物品可以被使用多次,所以遍历顺序与01背包有很大的不同,需要从前往后遍历背包;

又因为该题选择物品的顺序不做要求,及求组合数,所以要先遍历物品后背包

• 整体代码

int change(int amount, int* coins, int coinsSize)
{
    //初始化
    int dp[amount + 1];
    memset(dp, 0, sizeof(dp));
    dp[0] = 1;

    for (int i = 0; i < coinsSize; i++)  //先物品
    {
        for (int j = coins[i]; j <= amount; j++)  //后背包,从前往后
        {
            dp[j] += dp[j - coins[i]];
        }
    }

    return dp[amount];
}

 (2)322. 零钱兑换

leetcode传送➡️https://leetcode.cn/problems/coin-change/

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11

输出:3
 
解释:11 = 5 + 5 + 1  

1. dp数组定义

同样是零钱兑换,但是Ⅰ与Ⅱ有很大的区别;

由题可知,这里的dp的含义为:装满背包所需的最少物品(钱币)数量;

2. 递推公式 

重新选择一个物品来装满这个背包 与 已经被装满的背包比较取较小值作为装满这个背包的最少钱币数量,及递推公式为:

dp[j] = MIN(dp[j - coins[i]] + 1, dp[j]);

注意这里的+1,并不是方法数,而表示多加了一枚硬币 

3. 初始化

• 与其它题不同的是,这里需要求最小值,所以我们不能对dp都初始化为0,而需要对其初始化为一个较大值:

这里可以初始化为MAX_INT

也可以初始化为目标值加一(amount+1),因为钱币值为整数,最小为1,所以要达到目标和的最大钱币数肯定比amount+1小

• dp[0]表示装满背包体积为0所需最小钱币数量,自然初始化为0

4. 遍历顺序

遍历顺序与上一题相同,这类组合加完全背包均可考虑这样的遍历顺序

• 整体代码

#define MIN(x,y) (x) > (y) ? (y) : (x)

int coinChange(int* coins, int coinsSize, int amount)
{
    //初始化
    int MAX = amount + 1;
    int dp[amount + 1];
    for (int i = 0; i <= amount; i++)
    {
        dp[i] = MAX;
    }
    dp[0] = 0;
    
    for (int i = 0; i < coinsSize; i++)
    {
        for (int j = coins[i]; j <= amount; j++)
        {
            if (dp[j - coins[i]] != MAX)   //排除最大值+1
            {
                dp[j] = MIN(dp[j - coins[i]] + 1, dp[j]);
            }

        }
    }

    if (dp[amount] == MAX)    这里表示没有能使背包装满的钱币组合
        return -1;   


    return dp[amount];
}

(3)377. 组合总和 Ⅳ

leetcode传送➡️https://leetcode.cn/problems/combination-sum-iv/

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。

题目数据保证答案符合 32 位整数范围。

示例 1:

输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

1. dp数组定义

由题,dp背包大小为target;

这道题的dp含义与打家劫舍Ⅱ的dp含义相似:装满背包的方法数(组合数)

2. 递推公式 

本题递推公式与打家劫舍Ⅱ也是相同的,也是求方法数:

dp[i] += dp[i - nums[j]];

3. 初始化

同样的道理,求方法数:dp[0]初始化为1,其余初始化为0

4. 遍历顺序

通读一遍题,其实他叫我们求的是不同的排列数;

所以与之前不同的是,我们需要先遍历背包,后遍历物品

至于为什么呢?先便利背包,得到一个背包,由这个背包遍历物品,每个物品(体积比背包小)都能由另一背包递推到该背包中,得到的便是排列的数量

大家可以举一些例子来区别排列与组合两者遍历顺序的区别,这点是非常重要的。✔️

•整体代码

#define INT_MAX 2147483647
int combinationSum4(int* nums, int numsSize, int target) {
    //初始化
    int dp[target + 1];
    memset(dp, 0, sizeof(dp));
    dp[0] = 1;
   
    for (int i = 1; i <= target; i++)   //先背包,从前往后
    {
        for (int j = 0; j < numsSize; j++)   //后物品
        {
            if (nums[j] <= i && (dp[i] + dp[i - nums[j]]) < INT_MAX)  //排除
            {
                dp[i] += dp[i - nums[j]];
            }
        }
    }
    return dp[target];
}

※拓展

在学习动态规划基础时,相信大家做过青蛙跳台阶的题目,那道题是青蛙一次可以跳一阶或者两阶,那么拓展到 n阶 该怎么做呢?其实解法与这道题是一模一样的,大家可以自行根据这道题的解法摸索出来。

01背包与完全背包及解题总结💐💐💐

递推公式:总结01背包与完全背包的题型,主要是求最大价值和方法个数,这里我们将这两类的递推公式模板总结出来,具体问题还是要具体分析

①最大价值题

dp[j] = max(dp[j], dp[j-weight[i]] + value[i])

②方法个数题:

dp[j] += dp[j - nums[i]]

初始化:通过上面这一系列题,我们也发现了初始化的一些规律,具体问题还是要具体分析

①最大价值题:

memset(dp,0,sizeof(dp));

②方法个数题:

 memset(dp, 0, sizeof(dp));
 dp[0] = 1;

遍历顺序:

① 01背包:从后往前遍历背包;     完全背包:从前往后遍历背包;

② 求组合数:先遍历物品后遍历背包    求排列数:先遍历背包后遍历物品。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dusong_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值