动态规划-----背包类问题(0-1背包与完全背包)详解

这篇具有很好参考价值的文章主要介绍了动态规划-----背包类问题(0-1背包与完全背包)详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

什么是背包问题?

动态规划问题的一般解决办法:

0-1背包问题:

0 - 1背包类问题  分割等和子集: 

完全背包问题: 

完全背包类问题 零钱兑换II:


什么是背包问题?

背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。相似问题经常出现在商业、组合数学,计算复杂性理论、密码学和应用数学等领域中。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V?它是在1978年由Merkle和Hellman提出的。

动态规划问题的一般解决办法:

动态规划,无非就是利用历史记录,来避免我们的重复计算。而这些历史记录,我们得需要一些变量来保存,一般是用一维数组或者二维数组来保存。下面我们先来讲下做动态规划题很重要的三个步骤:

  • 🧐 步骤一:定义dp数组元素的含义
  • 🧐步骤二:找出数组元素之间的关系式(也就是我们所熟知的状态转移方程)
  • 🧐第三步骤:找出初始值(base case)

接下来的题目我们会按照这三个步骤来解释说明

前言:本文包含动态规划中的经典背包问题,有关背包问题的描述如下:

在动态规划中,背包问题是一个经典的优化问题,它可以分为0-1背包问题和完全背包问题两种类型。下面我们就来看看这两个问题:

0-1背包问题:

问题描述:

给你一个可装载重量为 W 的背包和 N 个物品,每个物品有重量和价值两个属性。其中第 i 个物品的重量为 wt[i],价值为 val[i]。现在让你用这个背包装物品,每个物品只能用一次,在不超过被包容量的前提下,最多能装的价值是多少?

动态规划-----背包类问题(0-1背包与完全背包)详解,java数据结构与算法,动态规划,算法

  • 🧐 步骤一:定义dp数组元素的含义:

由于状态有两个,就是「背包的容量」和「可选择的物品」,这里我们就需要用到一个二维的dp

数组,如下为dp数组的定义:

🦉🦉🦉dp[i][w] 的定义如下:对于前 i 个物品,当前背包的容量为 w,这种情况下可以装的最大价值是 dp[i][w]

  • 🧐步骤二:找出数组元素之间的关系式(也就是我们所熟知的状态转移方程)

1.如果你没有把这第 i 个物品装入背包,那么很显然,最大价值 dp[i][w] 应该等于 dp[i-1][w],继承之前的结果(翻译一下就是不装入第i个物品,相当于对前 i - 1 个物品进行选择,对应此时的背包容量w)。即此时的状态转移方程是:dp[ i ][ w ] = dp[ i - 1 ][ w ]

2.如果你把这第 i 个物品装入了背包,此时背包剩余容量为 w - wt[ i - 1 ](wt数组下标是从0开始的, wt[ i - 1 ] 相当于第 i 个物品的重量,val 也一样)

则此时的状态转移方程是:dp[ i ][ w ] = dp[ i - 1 ][ w - wt[ i - 1] ] + val[ i - 1 ]

  • 🧐第三步骤:找出初始值(base case):

这题的base case 相对简单,当物品个数为0或则背包当前容量为0时,dp[ i ][ w ] 都等于0

按照上述的状态转移方程,我们可以填出对应dp表格(以图中的例子为例):

动态规划-----背包类问题(0-1背包与完全背包)详解,java数据结构与算法,动态规划,算法

有了上述铺垫后,动态规划的代码就很好实现了,具体代码如下:

int knapsack(int W, int N, int[] wt, int[] val) {
    assert N == wt.length;
    // base case 已初始化,数组自动全部初始化为0
    int[][] dp = new int[N + 1][W + 1];
    for (int i = 1; i <= N; i++) {
        for (int w = 1; w <= W; w++) {
            if (w - wt[i - 1] < 0) {
                // 这种情况下只能选择不装入背包
                dp[i][w] = dp[i - 1][w];
            } else {
                // 装入或者不装入背包,择优
                dp[i][w] = Math.max(
                    dp[i - 1][w - wt[i-1]] + val[i-1], 
                    dp[i - 1][w]
                );
            }
        }
    }
    
    return dp[N][W];
}

有了上面的一定了解后,我们来看看0 - 1背包的类似题:

0 - 1背包类问题  分割等和子集: 

看一下力扣第 416 题「分割等和子集open in new window」:

题目描述:输入一个只包含正整数的非空数组 nums,请你写一个算法,判断这个数组是否可以被分割成两个子集,使得两个子集的元素和相等。对应函数签名如下:

动态规划-----背包类问题(0-1背包与完全背包)详解,java数据结构与算法,动态规划,算法

我们可以将这个问题转化为0 - 1背包问题,具体做法:

这个问题相当于给一个可装载重量为 sum / 2 的背包和 N 个物品,每个物品的重量为 nums[i]。现在让你装物品,是否存在一种装法,能够恰好将背包装满?按照上述解题思路就是:

  • 🧐 步骤一:定义dp数组元素的含义:

dp[i][j] = x 表示,对于前 i 个物品(i 从 1 开始计数),当前背包的容量为 j 时,若 x 为 true,则说明可以恰好将背包装满,若 x 为 false,则说明不能恰好将背包装满

  • 🧐步骤二:找出数组元素之间的关系式(也就是我们所熟知的状态转移方程)

与上面类似,这里就直接给出来了:

1.不把这第 i 个物品装入背包:dp[ i ][ j ] = dp[ i - 1 ][ j ]

2.把这第i个物品装入背包:dp[ i ][ j ] = dp[ i - 1][ j - nums[ i - 1 ] ]

  • 🧐第三步骤:找出初始值(base case):

当背包容量为0时(sum / 2= 0)这时无论物体有多少个都可以满足条件,就是什么都不装嘛

ok,接下来看完整代码:

boolean canPartition(int[] nums) {
    int sum = 0;
    for (int num : nums) sum += num;
    // 和为奇数时,不可能划分成两个和相等的集合
    if (sum % 2 != 0) return false;
    int n = nums.length;
    sum = sum / 2;
    boolean[][] dp = new boolean[n + 1][sum + 1];
    // base case
    for (int i = 0; i <= n; i++)
        dp[i][0] = true;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= sum; j++) {
            if (j - nums[i - 1] < 0) {
                // 背包容量不足,不能装入第 i 个物品
                dp[i][j] = dp[i - 1][j];
            } else {
                // 装入或不装入背包
                dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
            }
        }
    }
    return dp[n][sum];
}

完全背包问题: 

完全背包问题与0-1背包问题类似,但不同之处在于每个物品可以选择放入背包多次(数量无限),即每个物品的选择是一个无限的选择。我们给出对应的解题方法:

  • 🧐 步骤一:定义dp数组元素的含义:

若只使用前 i 个物品(可以重复使用),当背包容量为 j 时,能装入背包的最大价值为dp[i][w] 

  • 🧐步骤二:找出数组元素之间的关系式(也就是我们所熟知的状态转移方程)

1.不将第i个物品装入背包,此时:dp[ i ][ w ] = dp[ i - 1 ][ w ]

2.将第i个物品装入背包,此时:dp[ i ][ w ] = dp[ i ][ w -wt[ i - 1] ] + val[ i - 1 ] 

  • 🧐第三步骤:找出初始值(base case):

这题与0 - 1的bas背包的base case 一致,当物品个数为0或者背包当前容量为0时,dp[ i ][ w ] 都等于0

对应的动态规划代码为:

int fullBackpack(int W, int N, int[] wt, int[] val) {
    assert N == wt.length;
    // base case 已初始化,数组自动全部初始化为0
    int[][] dp = new int[N + 1][W + 1];
    for (int i = 1; i <= N; i++) {
        for (int w = 1; w <= W; w++) {
            if (w - wt[i - 1] < 0) {
                // 这种情况下只能选择不装入背包
                dp[i][w] = dp[i - 1][w];
            } else {
                // 装入或者不装入背包,择优
                dp[i][w] = Math.max(
                    dp[i][w - wt[i-1]] + val[i-1], 
                    dp[i - 1][w]
                );
            }
        }
    }
    
    return dp[N][W];
}

完全背包类问题 零钱兑换II:

这是力扣第 518 题「零钱兑换 IIopen in new window」,题目描述:

给定不同面额的硬币 coins 和一个总金额 amount,写一个函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。对应函数签名如下:

动态规划-----背包类问题(0-1背包与完全背包)详解,java数据结构与算法,动态规划,算法

我们可以把这个问题转化为完全背包问题的描述形式

有一个背包,最大容量为 amount,有一系列物品 coins,每个物品的重量为 coins[i]每个物品的数量无限。请问有多少种方法,能够把背包恰好装满?按照动态规划三步走:

  • 🧐 步骤一:定义dp数组元素的含义:

dp[i][j] 的定义如下:若只使用前 i 个物品(可以重复使用),当背包容量为 j 时,有 dp[i][j] 种方法可以装满背包。

  • 🧐步骤二:找出数组元素之间的关系式(也就是我们所熟知的状态转移方程)

1.如果你不把这第 i 个物品装入背包,也就是说你不使用 coins[i-1] 这个面值的硬币,那么凑出面额 j 的方法数 dp[i][j] 应该等于 dp[i-1][j],继承之前的结果

即状态转移方程为:dp[ i ][ j ] = dp[ i -1 ][ j ]

2.如果你把这第 i 个物品装入了背包,也就是说你使用 coins[i-1] 这个面值的硬币,那么 dp[i][j] 应该等于 dp[i][j-coins[i-1]]

即状态转移方程为:dp[ i ][ j ] = dp[ i ][ j - coins[ i - 1] ]

  • 🧐第三步骤:找出初始值(base case):

base case 为 dp[0][..] = 0, dp[..][0] = 1i = 0 代表不使用任何硬币面值,这种情况下显然无法凑出任何金额;j = 0 代表需要凑出的目标金额为 0,那么什么都不做就是唯一的一种凑法。

写出动态规划代码:

int change(int amount, int[] coins) {
    int n = coins.length;
    int[][] dp = int[n + 1][amount + 1];
    // base case
    for (int i = 0; i <= n; i++) 
        dp[i][0] = 1;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= amount; j++)
           //装入背包或者不装入,加起来
            if (j - coins[i-1] >= 0)
                dp[i][j] = dp[i - 1][j] 
                         + dp[i][j - coins[i-1]];
            else // < 0 只能选择不装入背包
                dp[i][j] = dp[i - 1][j];
    }
    return dp[n][amount];
}

结语: 写博客不仅仅是为了分享学习经历,同时这也有利于我巩固自己的知识点,总结该知识点,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进。同时也希望读者们不吝啬你们的点赞+收藏+关注,你们的鼓励是我创作的最大动力!

动态规划-----背包类问题(0-1背包与完全背包)详解,java数据结构与算法,动态规划,算法文章来源地址https://www.toymoban.com/news/detail-854031.html

到了这里,关于动态规划-----背包类问题(0-1背包与完全背包)详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【动态规划之完全背包问题】完全背包问题的通用解法与优化

    【动态规划之完全背包问题】完全背包问题的通用解法与优化

    ⭐️ 前面的话 ⭐️ 本篇文章将介绍动态规划中的背包问题——完全背包问题,前面我们已经介绍了0-1背包问题,其实完全背包问题就只改了0-1背包问题的一个条件,即物品可选择次数由一次改为无数次,仅此而已,下面我们就来开始介绍完全背包问题。 📒博客主页:未见

    2023年04月22日
    浏览(44)
  • 动态规划:完全背包问题

    动态规划:完全背包问题

    ACwing #3. 完全背包问题 完全背包问题和01背包问题很相似。 01背包问题每个物品只能选一个,而完全背包问题每个物品可以选无限次。 DP问题的关键是找到状态转移方程: ①定义f[i][j]表示从前 i 个物品中选择,体积为 j 的时候的最大价值。 ②那么转移方程f[i][j] = max(f[i - 1][j

    2023年04月19日
    浏览(15)
  • 动态规划——完全背包问题

    动态规划——完全背包问题

    由于本人实力尚浅,接触算法没多久,写这篇blog仅仅是想要提升自己对算法的理解,如果各位读者发现什么错误,恳请指正,希望和大家一起进步。(●’◡’●) 了解完全背包问题前可以先去看看01背包问题(良心正解),先了解这个基础问题会更有利于你了解下面的完全背

    2024年02月04日
    浏览(7)
  • 算法系列--动态规划--背包问题(3)--完全背包介绍

    算法系列--动态规划--背包问题(3)--完全背包介绍

    💕\\\"Su7\\\"💕 作者:Lvzi 文章主要内容:算法系列–动态规划–背包问题(3)–完全背包介绍 大家好,今天为大家带来的是 算法系列--动态规划--背包问题(3)--完全背包介绍 链接: 完全背包 可以发现完全背包问题和01背包问题还是特比相似的 分析: 完全背包问题 是 01背包问题 的推广

    2024年04月25日
    浏览(10)
  • 三十八、动态规划——背包问题( 01 背包 + 完全背包 + 多重背包 + 分组背包 + 优化)

    三十八、动态规划——背包问题( 01 背包 + 完全背包 + 多重背包 + 分组背包 + 优化)

    0 1 背包问题: 条件:N 个物品容量为 V 的背包,每件物品最多用 1 次,其中物品信息体积为 Vi,价值为 Wi。 目标:选出物品,使价值最大(不一定装满背包)。 特点:每件物品 最多只用 1 次 完全背包问题: 特点:每一件物品都有 无限个 多重背包问题: 特点:每个物品

    2024年02月07日
    浏览(10)
  • 力扣刷题-动态规划算法3:完全背包问题

    力扣刷题-动态规划算法3:完全背包问题

    问题描述: 1)有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。 2) 每件物品都有无限个(也就是可以放入背包多次) (比0-1背包多出的条件) 3) 求解将哪些物品装入背包里物品价值总和最大。 求解步骤: 1)首先遍历物品,然

    2023年04月13日
    浏览(51)
  • 动态规划:完全背包问题----中专生刷算法

    动态规划:完全背包问题----中专生刷算法

    需要基础:闫氏dp分析法,01背包问题 先去看一下01背包问题,再看完全背包 动态规划:选择dp及优化01背包问题-CSDN博客 做过01背包问题的同学会发现,完全背包问题的代码 在01背包基础上改动很小,但是里面的思想,有很大差距 题目 有 N 种物品和一个容量是 V的背包,每种物品

    2024年04月16日
    浏览(16)
  • 算法套路十四——动态规划之背包问题:01背包、完全背包及各种变形

    算法套路十四——动态规划之背包问题:01背包、完全背包及各种变形

    如果对递归、记忆化搜索及动态规划的概念与关系不太理解,可以前往阅读算法套路十三——动态规划DP入门 背包DP介绍:https://oi-wiki.org/dp/knapsack/ 0-1背包:有n个物品,第i个物品的体积为w[i],价值为v[i],每个物品至多选一个, 求体积和不超过capacity时的最大价值和,其中i从

    2024年02月10日
    浏览(36)
  • 动态规划:0-1背包、完全背包问题 | 详细原理解释 | 代码及优化(C++)

    动态规划:0-1背包、完全背包问题 | 详细原理解释 | 代码及优化(C++)

    目录 01背包 问题描述: 简单描述就是: 解析: 递推公式: dp数组的初始化: 遍历顺序: 图解: 实现代码: dp数组初始化: 遍历: 优化: 原理: 递推公式: 遍历顺序: 实现代码: 初始化: 遍历: 完全背包 问题描述: 解析: 实现代码:         01背包是在M件物品

    2024年02月11日
    浏览(11)
  • 算法篇——动态规划 完全和多重背包问题 (js版)

    算法篇——动态规划 完全和多重背包问题 (js版)

    01 背包 问题和 完全背包 问题的不同点在于,所有的物品只能 使用一次 ,判断  哪些物品  装进背包里 物品价值和 最大;而 完全背包 问题中,所有物品都能 使用n次 ,判断 哪个物品 装 n 个进去 物品价值和 最大。 01 背包的递推公式是: 【当然先遍历物品还是背包的容量

    2024年02月08日
    浏览(14)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包