当前位置: 首页 > news >正文

选取货物 - 题解(0-1背包问题)

题目描述

若干货物的信息记录于二维数组 goods 中,其中 goods[i] = [weight,value],表示第 i 个货物的价值为 value,重量为 weight。从中选取一些货物,求当选取的货物总重量不超过 maxWeight 时的货物最大价值总和。

示例:

输入:
goods = [[7,7],[5,1],[8,9],[5,7],[6,8]], maxWeight = 21输出: 24解释:
选取第 2、3、4个货物(下标从 0 开始),此时商品重量总和为 8 + 5 + 6 = 19,商品价值总和为 9 + 7 + 8 = 24。

提示:

  • 1 <= goods.length <= 100
  • 1 <= goods[i][0] <= 10^5
  • 1 <= goods[i][1] <= 100
  • 1 <= maxWeight <= 10^7

问题分析

这是一个典型的0-1背包问题,每个货物只能选择拿或不拿,不能部分选取。

问题特征:

  1. 物品有限:总共有 n 个货物
  2. 容量限制:背包最大承重为 maxWeight
  3. 每个物品有两个属性:重量和价值
  4. 目标:在不超过背包容量的前提下,最大化物品的总价值

决策过程:

对于每个货物,我们面临两个选择:

  • 选择该货物:获得其价值,但消耗背包容量
  • 不选择该货物:不获得价值,也不消耗容量

解题思路

动态规划方法

状态定义:

  • dp[i][w] 表示考虑前 i 个货物,背包容量为 w 时能获得的最大价值

状态转移方程:

dp[i][w] = max(dp[i-1][w],                           // 不选择第i个货物dp[i-1][w-weight[i]] + value[i]       // 选择第i个货物(如果容量足够)
)

边界条件:

  • dp[0][w] = 0(没有货物时,价值为0)
  • dp[i][0] = 0(背包容量为0时,价值为0)

算法过程

以示例 goods = [[7,7],[5,1],[8,9],[5,7],[6,8]], maxWeight = 21 为例:

动态规划表格构建过程

货物信息:
索引  重量  价值
0     7     7
1     5     1  
2     8     9
3     5     7
4     6     8

 构建dp表格(部分关键状态):

i\w0567810111213181921
0000000000000
1000777777777
2011778888888
30117999916161617
40778914161616162324
50788914161717222324

最终答案: dp[5][21] = 24

最优解构成

选择的货物:

  • 货物2:重量8,价值9
  • 货物3:重量5,价值7
  • 货物4:重量6,价值8
  • 总重量:8 + 5 + 6 = 19 ≤ 21 ✓
  • 总价值:9 + 7 + 8 = 24

算法实现

动态规划

Java版本
/*** 2. 选取货物* 动态规划*/
class Solution {public int maximumGoodsValue(int[][] goods, int maxWeight) {int n = goods.length;// dp[i][w] 表示考虑前i个货物,背包容量为w时的最大价值int[][] dp = new int[n + 1][maxWeight+1];for (int i = 1; i <= n; i++) {int weight = goods[i-1][0]; // 当前货物重量int value = goods[i-1][1]; // 当前货物价值// 遍历每个可能的背包容量for (int w = 0; w <= maxWeight; w++) {// 不选择当前货物dp[i][w] = dp[i-1][w];// 如果背包容量足够,考虑选择当前货物if (w >= weight) {dp[i][w] = Math.max(dp[i][w], dp[i-1][w-weight] + value);}}}return dp[n][maxWeight];}
}
C# 版本
/*** 2. 选取货物* 动态规划*/
public class Solution
{public int MaximumGoodsValue(int[][] goods, int maxWeight){int n = goods.Length;int[,] dp = new int[n + 1, maxWeight + 1];for (int i = 1; i <= n; i++){int weight = goods[i - 1][0];int value = goods[i - 1][1];for (int w = 0; w <= maxWeight; w++){dp[i, w] = dp[i - 1, w];if (w >= weight){dp[i, w] = Math.Max(dp[i, w], dp[i - 1, w - weight] + value);}}}return dp[n, maxWeight];}
}

空间优化

Java版本
/*** 2. 选取货物* 动态规划,空间优化*/
class Solution {public int maximumGoodsValue(int[][] goods, int maxWeight) {int n = goods.length;int[] dp = new int[maxWeight + 1];for (int i = 1; i <= n; i++) {int weight = goods[i - 1][0];int value = goods[i - 1][1];// 从后往前遍历,避免重复计算for (int w = maxWeight; w >= weight; w--) {dp[w] = Math.max(dp[w], dp[w - weight] + value);}}return dp[maxWeight];}
}
C# 版本
/*** 2. 选取货物* 动态规划,空间优化*/
public class Solution
{public int MaximumGoodsValue(int[][] goods, int maxWeight){int n = goods.Length;int[] dp = new int [maxWeight + 1];for (int i = 1; i <= n; i++){int weight = goods[i - 1][0];int value = goods[i - 1][1];for (int w = maxWeight; w >= weight; w--){dp[w] = Math.Max(dp[w], dp[w - weight] + value);}}return dp[maxWeight];}
}

复杂度分析

时间复杂度

  • 二维DP版本:O(n × maxWeight)
    • 需要填充 n × maxWeight 大小的表格
  • 一维优化版本:O(n × maxWeight)
    • 虽然空间优化了,但时间复杂度不变

空间复杂度

  • 二维DP版本:O(n × maxWeight)
  • 一维优化版本:O(maxWeight)

进一步优化思路:状态转换

核心洞察

观察到物品价值范围很小(最大100),而背包容量很大(最大10^7),我们可以转换DP的状态定义:

传统思路:

  • 状态:dp[w] = 容量为w时的最大价值
  • 转移:枚举容量,更新价值

优化思路:

  • 状态:dp[v] = 达到价值v所需的最小重量
  • 转移:枚举价值,更新重量

优化后的复杂度

  • 最大总价值:100(物品数) × 100(单价值) = 10,000
  • 时间复杂度:O(n × V) = O(100 × 10,000) = O(10^6) ,大幅优化
  • 空间复杂度:O(V) = O(10,000) ,显著减少

算法详解

状态转移过程

让我们通过示例来理解算法过程:

输入: goods = [[7,7],[5,1],[8,9],[5,7],[6,8]], maxWeight = 21

初始化
maxTotalValue = 5 × 100 = 500
dp = [0, ∞, ∞, ∞, ..., ∞]  // 长度为501
处理物品0:[7,7](重量7,价值7)
从 v=500 到 v=7:如果 dp[v-7] != ∞,则 dp[v] = min(dp[v], dp[v-7] + 7)v=7: dp[7] = min(∞, dp[0] + 7) = min(∞, 0 + 7) = 7结果:dp = [0, ∞, ∞, ∞, ∞, ∞, ∞, 7, ∞, ...]
含义:价值7需要重量7
处理物品1:[5,1](重量5,价值1)
v=8: dp[8] = min(∞, dp[7] + 5) = min(∞, 7 + 5) = 12
v=1: dp[1] = min(∞, dp[0] + 5) = min(∞, 0 + 5) = 5结果:dp = [0, 5, ∞, ∞, ∞, ∞, ∞, 7, 12, ∞, ...]
含义:价值1需要重量5,价值8需要重量12
继续处理剩余物品...

最终找到在重量限制21内能达到的最大价值。

关键思想解释

  • 状态定义转换:
    • 传统:dp[重量] = 最大价值
    • 优化:dp[价值] = 最小重量
  • 为什么逆序遍历:
    • 保证每个物品只被使用一次
    • 避免在同一轮更新中重复使用同一物品
  • 为什么这样优化有效:
    • 价值范围小(最大10,000)
    • 重量范围大(最大10^7)
    • 通过转换状态,将复杂度从O(重量)降到O(价值)

算法实现

Java 版本

import java.util.Arrays;/*** 2. 选取货物* 按价值DP*/
class Solution {public int maximumGoodsValue(int[][] goods, int maxWeight) {int n = goods.length;// 计算最大可能的总价值int maxTotalValue = n * 100;// dp[v] 表示达到价值v所需的最小重量// 初始化为无穷大,表示无法达到该价值int[] dp = new int[maxTotalValue + 1];Arrays.fill(dp, Integer.MAX_VALUE);// 价值为0时,重量也为0dp[0] = 0;for (int i = 1; i <= n; i++) {int weight = goods[i - 1][0]; // 当前货物重量int value = goods[i - 1][1]; // 当前货物价值// 从高价值向低价值遍历,避免重复使用同一物品for (int v = maxTotalValue; v >= value; v--) {// 如果可以达到价值 v-value,则可以通过添加当前物品达到价值vif (dp[v - value] != Integer.MAX_VALUE) {dp[v] = Math.min(dp[v], dp[v - value] + weight);}}}// 找到最大价值,使其对应的重量不超过maxWeightint res = 0;for (int v = maxTotalValue; v >= 0; v--) {if (dp[v] <= maxWeight) {res = v;break;}}return res;}
}

C# 版本

/*** 2. 选取货物* 按价值DP*/
public class Solution
{public int MaximumGoodsValue(int[][] goods, int maxWeight){int n = goods.Length;// 计算最大可能的总价值int maxTotalValue = 100 * n;// dp[v] 表示达到价值v所需的最小重量// 初始化为无穷大,表示无法达到该价值int[] dp = new int[maxTotalValue + 1];Array.Fill(dp, int.MaxValue);// 价值为0时,重量也为0dp[0] = 0;for (int i = 0; i < n; i++){int weight = goods[i][0];int value = goods[i][1];// 从高价值向低价值遍历,避免重复使用同一物品for (int v = maxTotalValue; v >= value; v--){// 如果可以达到价值 v-value,则可以通过添加当前物品达到价值vif (dp[v - value] != int.MaxValue){dp[v] = Math.Min(dp[v], dp[v - value] + weight);}}}// 找到最大价值,使其对应的重量不超过maxWeightint res = 0;for (int v = 0; v <= maxTotalValue; v++){if (dp[v] <= maxWeight){res = v;}}return res;}
}

复杂度分析

  • 时间复杂度:O(n × V) = O(100 × 10,000) = O(10^6)
  • 空间复杂度:O(V) = O(10,000)

相比传统方法的O(10^9)时间复杂度,这是一个巨大的优化!

相关文章:

  • Python学习(8) ----- Python的类与对象
  • Shellshock漏洞与永恒之蓝(WannaCry)勒索病毒深度分析
  • 记录:外扩GPIOD访问报警告
  • 嵌入式学习Day34
  • DAY 26 函数专题1
  • (每日一道算法题)子集
  • 智能SQL优化工具PawSQL月度更新 | 推出免费私域部署社区版
  • Day09_刷题niuke20250609
  • 高保真组件库:上传
  • 问题复盘-当前日志组损坏问题
  • 源雀Scrm开源版:AI客服会话总结 功能发布
  • PyTorch学习路径与基础实践指南
  • 76. 最小覆盖子串 Java版
  • tomcat核心组件
  • TCP和UDP区别
  • 2025-05-08-deepseek本地化部署
  • Geekbench AI 命令行工具的安装
  • 开始新的认识,对worldquant(50alpha)
  • Windows 环境下,使用 ESP32 JTAG 接口进行固件下载
  • LLM基础5_从零开始实现 GPT 模型
  • 做网站买狗怎么样/搜索引擎谷歌
  • 百度验证网站有什么用/百度seo关键词点击软件
  • 网站源文件下载/日本关键词热搜榜
  • wordpress自定义数据/广东seo快速排名
  • 做设计找参考的设计网站有哪些/谷歌地图下载
  • 做葡萄牙语网站/海外独立站