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

代码随想录算法训练营第三十天|0/1背包问题

二维

有n件物品和一个最多能背重量为w的背包。第i件物品的重量是w[i],得到的价值是v[i]。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

1.确定dp数组以及下标的含义:

dp[i][j]表示从下标为[0~i]的物品里任意取,放进容量为j的背包,能得到最大的价值。

2.确定递推公式:

对于dp[i][j]有两种情况:放物品i;不放物品i。

如果不放物品i,dp[i][j]=dp[i-1][j];

如果放物品i,那么背包要先留出物品i的容量,背包容量变为j-w[i],所以dp[i][j]=dp[i-1][j-w[i]]+v[i]。

所以递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])

3.初始化:

首先如果背包容量j为0的话,背包价值一定为0,也就是dp[i][0]=0。

由递推公式可以看出i是由i-1推导出来的,那么i为0的时候就一定要初始化。所以当j<w[0]时,dp[0][j]=0;当j>=w[0]时,dp[0][j]=v[0]。

for (int i = 1; i < w.length; i++) {  // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略dp[i][0] = 0;
}
for (int j = w[0]; j <= bagweight; j++) {dp[0][j] = v[0];
}

4.确定遍历顺序:

有两个维度的遍历:物品与背包重量。

先遍历物品,然后再遍历背包重量:

for (int i = 1; i < n; i++) {for (int j = 0; j <= bagweight; j++) {if (j < weight[i]) {dp[i][j] = dp[i - 1][j];} else {dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);}}}

5.举例推导dp数组:

 

一维

1.确定dp数组以及下标的含义:

dp[j]表示容量为j的背包所背的物品价值最大可以为dp[j] 。

2.确定递推公式:

二维dp数组的递推公式为:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])。可以发现如果把dp[i-1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i][j] = max(dp[i][j], dp[i][j - w[i]] + v[i])

所以可以把i这个维度去掉,也就是:dp[j] = max(dp[j], dp[j - w[i]] + v[i])

3.初始化:默认都初始化为0即可。

4.确定遍历顺序:

先遍历物品,再遍历背包容量:

for (int i = 0; i < n ; i++) {for (int j = bagweight ; j >= w[i]; j--) {dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);}}

这里可以看出,一维dp遍历背包是从大到小遍历的,这和二维dp遍历不同,为什么?

如果背包正序遍历,物品i可能被放入多次。

比如:物品0的重量weight[0] = 1,价值value[0] = 15,如果正序遍历:

dp[1] = dp[1 - w[0]] + v[0] = 15

dp[2] = dp[2 - w[0]] + v[0] = 30

那么物品0就被放入了两次,所以不能正序遍历。

而倒序遍历:

dp[2] = dp[2 - w[0]] + v[0] = 15 (dp数组已经都初始化为0)

dp[1] = dp[1 - w[0]] + v[0] = 15

这样就能保证每种物品只取一次。

而二维dp遍历不需要倒序是因为dp[i][j]都是通过上一层dp[i-1][j]计算而来的,本层的dp[i][j]是不会被覆盖的。

5.举例推导dp数组:还是之前的例子

LeetCode 416 分割等和子集

题目链接: 416. 分割等和子集 - 力扣(LeetCode)

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

首先求出nums数组的元素和ans,如果ans为奇数,直接返回false即可。 

 nums数组相当于0/1背包问题中的容量数组和价值数组,判断能否将nums数组分割成链各个元素和相等的自己,那就相当于判断能否将容量为ans/2的背包装满。

因为容量和价值是相等的,所以最后容量为ans/2的背包的最大价值为ans/2,说明能装满,返回true,否则返回false。本质和0/1背包问题是一样的。

代码如下:

class Solution {public boolean canPartition(int[] nums) {int ans=0;int n=nums.length;for(int i=0;i<n;i++)ans+=nums[i];if(ans%2==1)return false;int[] dp=new int[ans/2+1];for(int i=0;i<n;i++){for(int j=ans/2;j>=nums[i];j--){dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]);}}if(dp[ans/2]==ans/2)return true;else return false;}
}
http://www.dtcms.com/a/304372.html

相关文章:

  • 如何保证DoIP的网络安全?
  • rtp、rtcp、rtsp、rtmp协议详解
  • 嵌入式学习日志(十一)
  • 【windows系统服务端ssh免密登录配置坑】
  • 低空经济应用-无人机拉格朗日粒子追踪技术
  • 阿里云上进行k8s集群的配置
  • 电脑没有声音了怎么恢复 快速解决音频故障
  • 使用 Databend Cloud 归档 OceanBase 数据数据库
  • ElasticStack技术栈概述及Elasticsearch8.2.2集群部署并更换JDK版本为openjdk-17
  • 路由器路由协议详解:从 RIP 到 OSPF 的技术演进
  • TRACERT命令
  • 迅为RK3588开发板安卓GPIO调用-APP运行测试
  • HarmonyOS-ArkUI Web控件基础铺垫6--TCP协议- 流量控制算法与拥塞控制算法
  • LeetCode Hot 100 搜索二维矩阵
  • 抽象工厂模式 Abastract Factory Pattern
  • 从本地 Docker 部署的 Dify 中导出知识库内容(1.6版本亲测有效)
  • 设计一个高可用、可拓展、监控报警系统,使用普罗米修斯和grafana,并给出go实现
  • 无穿戴动作捕捉技术:驱动历史活化、乐园叙事与教育沉浸的文旅利器
  • JVM知识点(2)
  • 从协议栈到ath12k_mac_op_tx的完整调用路径
  • Leetcode——41. 缺失的第一个正数
  • 前端学习日记(十五)
  • 深入理解图像插值:从原理到应用
  • 答题抽奖活动小程序技术复盘
  • unittest错误重跑与测试用例跳过机制
  • 操作系统-lecture2(操作系统结构)
  • Unity的GameObject.Instantiate的使用
  • 津发科技带你了解皮肤电信号中的SCL与SCR
  • SuperClaude Framework 使用指南
  • Ubuntu20.04子系统