LeeCode 动态规划(二)
01背包问题
题目描述
有
n
件物品和容量为w
的背包,给你两个数组weights
和values
,分别表示第i
件物品的重量和价值,每件物品只能使用一次,求解将哪些物品装入背包可使得物品价值总和最大?
建立模型
- 确定dp数组及下标的含义,数组的含义为从第
[0, i - 1]
个物品中选择若干个物品,其重量和小于等于j
的最大价值。 - 初始化dp数组,
dp[i][0] = 0 (0 < i < length)
,即背包容量为0的最大价值为0;dp[0][j] = 0
,即没有物品选择时最大价值为0。 - 确定递推公式:
dp[i][j] = Math.max(dp[i - 1][j - weighti[i]] + values[i], dp[i - 1][j]) (weights[i] <= j)
,左边表示选择当前物品,右边表示不选择当前物品;
dp[i][j] = dp[i - 1][j] (weights[i] > j)
,当前物品重量大于背包容量,无法选择。 - 确定遍历顺序,外循环遍历物品
i -> 0 ~ length-1
,内循环遍历背包容量j -> 1 ~ bagSize
,此情况下内外循环的顺序交换不影响结果。
代码实现
public int zeroOneBagProblem(int[] weights, int[] values, int bagSize) {
int n = weights.length;
int[][] dp = new int[n + 1][bagSize + 1];
/**
* 这两步初始化其实可以省略(定义数组时默认值为0)
* 但初次学习背包问题就做了显式初始化。
*/
for (int i = 0; i <= n; i++) {
dp[i][0] = 0;
}
for (int j = 0; j <= bagSize; j++) {
dp[0][j] = 0;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= bagSize; j++) {
if (j >= weights[i - 1]) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
}
else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n][bagSize];
}
代码优化
状态压缩:对于递推公式 dp[i][j] = Math.max(dp[i - 1][j - weighti[i]] + values[i], dp[i - 1][j])
,第 i
层只和第 i - 1
层的状态有关,可以只用一个一维数组表示。
public int zeroOneBagProblem(int[] weights, int[] values, int bagSize) {
int n = weights.length;
/**
* 数组的含义为背包容量j的最大价值总和
*/
int[] dp = new int[bagSize + 1];
for (int i = 0; i < length; i++) {
/**
* 必须倒序遍历背包容量以保证 dp[j - weights[i]] 是上一层的状态,而不是当前层
* 否则会出现同一物品多次放入的状态
*/
for (int j = bagSize, j >= weight[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);
}
}
return dp[bagSize];
}
LeeCode 416:分割等和子集
题目描述
给你一个只包含正整数的非空数组
nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
建立模型
本题表面是一个回溯的问题,对于每一个元素可以选择添加或者不添加到集合中,回溯过程中判断当前集合的元素和是否等于 sum / 2
。
如何把该问题转化为 01背包问题 呢?
分割数组得到两个等和的子集 \(\Leftrightarrow\) 能否从数组中选择一些元素使其和等于整个数组的一半,且每个元素只能选择一次。
物品 \(\rightarrow\) 数组中的元素,价值和重量都是 nums[i]
背包容量 \(\rightarrow\) sum / 2
- 确定dp数组及下标含义,数组的含义为从
nums[0] ~ nums[i]
中选取若干整数,是否存在和等于j
的情况。 - 初始化dp数组,
dp[i][0] = true
\(\Rightarrow\) 不选任何元素可使和为0,dp[0][nums[0]] = true
\(\Rightarrow\) 只有一个元素可选时,只选该元素对应的和为 true。 - 确定递推公式:
\[\begin{cases} dp[i][j] = dp[i - 1][j]\ ||\ dp[i - 1][j - nums[i]] \quad (j >= nums[i])\\ dp[i][j] = dp[i - 1][j] \quad (j < nums[i]) \end{cases}
\] - 确定遍历顺序,外循环遍历物品
i -> 1 ~ n
,内循环遍历背包容量j -> 1 ~ sum / 2
代码实现
public boolean canPartition(int[] nums) {
// 考虑特殊情况
if (nums.length == 1) {
return false;
}
int sum = 0;
int max_value = 0;
for (int num : nums) {
sum += num;
max_value = Math.max(num, max_value);
}
if (sum % 2 != 0 || max_value > sum / 2) {
return false;
}
int target = sum / 2;
int n = nums.length;
// 创建 dp 数组
boolean[][] dp = new boolean[n][target + 1];
for (int i = 0; i < n; i++) {
dp[i][0] = true;
}
dp[0][nums[0]] = true;
for (int i = 1; i < n; i++) {
for (int j = 1; j <= target; j++) {
if (j >= num[i]) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - num[i]];
}
else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n - 1][target];
}
/**
* 一维dp数组实现
*/
public boolean canPartition(int[] nums) {
// 考虑特殊情况
if (nums.length == 1) {
return false;
}
int sum = 0;
int max_value = 0;
for (int num : nums) {
sum += num;
max_value = Math.max(num, max_value);
}
if (sum % 2 != 0 || max_value > sum / 2) {
return false;
}
int target = sum / 2;
int n = nums.length;
// 创建 dp 数组
boolean[] dp = new boolean[target + 1];
for (int i = 0; i < n; i++) {
dp[0] = true;
}
for (int i = 0; i < n; i++) {
for (int j = target; j >= nums[i]; j--) {
dp[j] = dp[j] || dp[j - nums[i]];
}
}
return dp[target];
}
LeeCode 1049:最后一块石头的重量II
题目描述
有一堆石头,用整数数组
stones
表示。其中stones[i]
表示第 i 块石头的重量。每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为
x
和y
,且x <= y
。那么粉碎的可能结果如下:如果
x == y
,那么两块石头都会被完全粉碎;
如果x != y
,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。
建立模型
问题转化 \(\Rightarrow\) 01背包问题
该问题描述等价于选取若干石头使其重量小于等于 sum/2 的最大值,sum
为 stones
数组石头的重量总和。选取石头的重量越接近 sum / 2
,最近剩余的石头重量就越小。
物品 \(\rightarrow\) 石头,价值和重量都是 stones[i]
,且每个石头只能选择一次
背包容量 \(\rightarrow\) sum/2
代码实现
public int lastStoneWeightII(int[] stones) {
int sum = 0;
for (int stone : stones) {
sum += stone;
}
int target = sum / 2;
int[] dp = new int[target + 1];
for (int i = 0; i < stones.length; i++) {
for (int j = target; j >= stones[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
}
}
return sum - 2 * dp[target];
}
LeeCode 494:目标和
题目描述
给你一个整数数组
nums
和一个整数target
。向数组中的每个整数前添加
+
或-
,然后串联起所有整数,可以构造一个 表达式 :例如,
nums = [2, 1]
,可以在2
之前添加+
,在1
之前添加-
',然后串联起来得到表达式 "+2-1" 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
建立模型
问题转化 \(\Rightarrow\) 01背包问题
该问题描述等价于在数组中选择若干个元素使其和等于 (sum - target) / 2
的不同方法数。我们只需要给这些元素添加负号,数组中其它所有元素添加正号,则可使这个表达式的运算结果为 target
。
物品 \(\rightarrow\) 整数数组,重量和价值都是nums[i]
,且每个数字只能选择一次
背包容量 \(\rightarrow\) (sum - target) / 2
代码实现
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum < Math.abs(target) || (sum - target) % 2 != 0) {
return 0;
}
int cur = (sum - target) / 2;
// 数组的含义为从数组中选择元素使其和为 j 的方法数。
int[] dp = new int[cur + 1];
// dp[0][0] = 1, 表示没有任何物品可以选择且和为0的方法数
dp[0] = 1
for (int i = 0; i < nums.length; i++) {
for (int j = cur; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[cur];
}
LeeCode 474:一和零
题目描述
给你一个二进制字符串数组
strs
和两个整数m
和n
。请你找出并返回
strs
的最大子集的长度,该子集中 最多 有m
个0
和n
个1
。如果
x
的所有元素也是y
的元素,集合x
是集合y
的 子集 。
建立模型
问题转化 \(\Rightarrow\) 01背包问题
本题在经典背包问题的基础上,背包容量被划分为两个维度(0的容量和1的容量),做法是和01背包问题完全一致的。
- 确定dp数组及下标的含义,数组的含义为最多有
j
个0
和k
个1
的最大子集长度 - 初始化dp数组
dp[0][0] = 0
- 确定递推公式:
\[dp[j][k] = Math.max(dp[j][k], dp[j - countZero][k - countOne])
\] - 确定遍历顺序,外循环遍历物品
i->0 ~ length-1
,内循环遍历背包容量。
代码实现
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i < strs.length; i++) {
String s = strs[i];
int countZero = getOneZero(s)[0], countOne = getOneZero(s)[1];
for (int j = m; j >= countZero; j--) {
for (int k = n; k >= countOne; k--) {
dp[j][k] = Math.max(dp[j][k], dp[j - countZero][k - countOne] + 1);
}
}
}
return dp[m][n];
}
public int[] getOneZero(String s) {
int[] res = new int[2];
for (int i = 0; i < s.length(); i++) {
res[s.charAt(i) - '0'] += 1;
}
return res;
}
LeeCode 动态规划(二)的更多相关文章
- 【CodeForces】713 D. Animals and Puzzle 动态规划+二维ST表
[题目]D. Animals and Puzzle [题意]给定n*m的01矩阵,Q次询问某个子矩阵内的最大正方形全1子矩阵边长.n,m<=1000,Q<=10^6. [算法]动态规划DP ...
- 【洛谷】【动态规划(二维)】P1508 Likecloud-吃、吃、吃
[题目描述:] 正处在某一特定时期之中的李大水牛由于消化系统比较发达,最近一直处在饥饿的状态中.某日上课,正当他饿得头昏眼花之时,眼前突然闪现出了一个n*m(n and m<=200)的矩型的巨 ...
- 【动态规划/二维背包问题】mr355-三角形牧场
应该也是USACO的题目?同样没有找到具体出处. [题目大意] 和所有人一样,奶牛喜欢变化.它们正在设想新造型牧场.奶牛建筑师Hei想建造围有漂亮白色栅栏的三角形牧场.她拥有N(3≤N≤40)块木板, ...
- [ACM_动态规划] POJ 1050 To the Max ( 动态规划 二维 最大连续和 最大子矩阵)
Description Given a two-dimensional array of positive and negative integers, a sub-rectangle is any ...
- 动态规划(二维背包问题):UVAoj 473
Raucous Rockers You just inherited the rights to n previously unreleased songs recorded by the pop ...
- 【洛谷】【动态规划/二维背包】P1855 榨取kkksc03
[题目描述:] ... (宣传luogu2的内容被自动省略) 洛谷的运营组决定,如果...,那么他可以浪费掉kkksc03的一些时间的同时消耗掉kkksc03的一些金钱以满足自己的一个愿望. Kkks ...
- 动态规划(二)HDU1114
1.题目来源HDU1114 Sample Input 3 10 110 2 1 1 30 50 10 110 2 1 1 50 30 1 6 2 10 3 20 4 Sample Output The ...
- 动态规划二:最长公共子序列(LCS)
1.两个子序列:X={x1,x2....xm},Y={y1,y2....yn},设Z={z1,z2...zk}. 2.最优子结构: 1)如果xm=yn ,则zk=xm=yn且Zk-1是Xm-1和Yn- ...
- 【学习笔记】动态规划—各种 DP 优化
[学习笔记]动态规划-各种 DP 优化 [大前言] 个人认为贪心,\(dp\) 是最难的,每次遇到题完全不知道该怎么办,看了题解后又瞬间恍然大悟(TAT).这篇文章也是花了我差不多一个月时间才全部完成 ...
- (lintcode全部题目解答之)九章算法之算法班题目全解(附容易犯的错误)
--------------------------------------------------------------- 本文使用方法:所有题目,只需要把标题输入lintcode就能找到.主要是 ...
随机推荐
- web.xml文件报错'org.springframework.web.filter.CharacterEncodingFilter' is not assignable to 'javax.servlet.Servlet,jakarta.servlet.Servlet'
在web.xml文件中出现下列错误:'org.springframework.web.filter.CharacterEncodingFilter' is not assignable to 'jav ...
- 实验二 实验二 Linux系统简单文件操作命令
项目 内容 这个作业属于哪个课程 <班级课程的主页链接> 这个作业的要求在哪里 <作业要求链接接地址> 学号-姓名 15043109吴小怀 作业学习目标 学习在Linux系统终 ...
- CH573 CH582 OTA例程讲解(使用固定库+扩大APP空间)
例程中提供的两种OTA就不过多介绍了,在BLE目录下有一个PDF专门讲解:WCH蓝牙空中升级(BLE OTA) 方式一是带库升级,整个codeflash分成四个区域,Jump IAP,APP,OTA, ...
- 转载C#文件下载的实现
一.//TransmitFile实现下载 protected void Button1_Click(object sender, EventArgs e) { /* ...
- loadrunner写webservice接口
先用soupUI调试 fiddler抓包 然后再写: web_custom_request("createSoapOrder", "URL=http:/ ...
- 爬快手,graphql查询语言
graphql查询语言:https://blog.csdn.net/qq_41882147/article/details/82966783 即:前端调用同一个接口传入不同的操作,得到不同的返回值 一 ...
- pytest用例管理框架实战(基础篇)
先安装pip install pytest pytest用例管理框架 默认规则: 1.py文件必须以test_开头或者_test结尾 2.类名必须以test开头 3.测试用例必须以test_开头 ge ...
- async 与 Thread 的错误结合
在 TAP 出现之前,我们可以通过 Thread 来完成一些线程操作,从而实现多线程和异步操作.在 TAP 出现之后,有时候为了更高精度的控制线程,我们还是会使用到 Thread .文本讲介绍一种错误 ...
- 微软NewBing真是Niubility
这是本人2012年的拙作: 晨兮,闻风雨,后而雷鸣电闪.迟不可再三,若故无食.然何如耶?雨大风狂,单车奈何?公交卡空,恐时不予我也.不免叹也,天亦不予我! 而后出, ...
- AD域安全攻防实践(附攻防矩阵图)
以域控为基础架构,通过域控实现对用户和计算机资源的统一管理,带来便利的同时也成为了最受攻击者重点攻击的集权系统. 01.攻击篇 针对域控的攻击技术,在Windows通用攻击技术的基础上自成一套技术体系 ...