01背包问题

题目描述

n 件物品和容量为 w 的背包,给你两个数组 weightsvalues ,分别表示第 i 件物品的重量和价值,每件物品只能使用一次,求解将哪些物品装入背包可使得物品价值总和最大?

建立模型

  1. 确定dp数组及下标的含义,数组的含义为从第 [0, i - 1] 个物品中选择若干个物品,其重量和小于等于 j 的最大价值。
  2. 初始化dp数组,dp[i][0] = 0 (0 < i < length),即背包容量为0的最大价值为0;dp[0][j] = 0,即没有物品选择时最大价值为0。
  3. 确定递推公式:

    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),当前物品重量大于背包容量,无法选择。
  4. 确定遍历顺序,外循环遍历物品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 的最大值sumstones 数组石头的重量总和。选取石头的重量越接近 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 和两个整数 mn

请你找出并返回 strs 的最大子集的长度,该子集中 最多m0n1

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y子集

建立模型

问题转化 \(\Rightarrow\) 01背包问题

本题在经典背包问题的基础上,背包容量被划分为两个维度(0的容量和1的容量),做法是和01背包问题完全一致的。

  • 确定dp数组及下标的含义,数组的含义为最多有 j0k1 的最大子集长度
  • 初始化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 动态规划(二)的更多相关文章

  1. 【CodeForces】713 D. Animals and Puzzle 动态规划+二维ST表

    [题目]D. Animals and Puzzle [题意]给定n*m的01矩阵,Q次询问某个子矩阵内的最大正方形全1子矩阵边长.n,m<=1000,Q<=10^6. [算法]动态规划DP ...

  2. 【洛谷】【动态规划(二维)】P1508 Likecloud-吃、吃、吃

    [题目描述:] 正处在某一特定时期之中的李大水牛由于消化系统比较发达,最近一直处在饥饿的状态中.某日上课,正当他饿得头昏眼花之时,眼前突然闪现出了一个n*m(n and m<=200)的矩型的巨 ...

  3. 【动态规划/二维背包问题】mr355-三角形牧场

    应该也是USACO的题目?同样没有找到具体出处. [题目大意] 和所有人一样,奶牛喜欢变化.它们正在设想新造型牧场.奶牛建筑师Hei想建造围有漂亮白色栅栏的三角形牧场.她拥有N(3≤N≤40)块木板, ...

  4. [ACM_动态规划] POJ 1050 To the Max ( 动态规划 二维 最大连续和 最大子矩阵)

    Description Given a two-dimensional array of positive and negative integers, a sub-rectangle is any ...

  5. 动态规划(二维背包问题):UVAoj 473

     Raucous Rockers  You just inherited the rights to n previously unreleased songs recorded by the pop ...

  6. 【洛谷】【动态规划/二维背包】P1855 榨取kkksc03

    [题目描述:] ... (宣传luogu2的内容被自动省略) 洛谷的运营组决定,如果...,那么他可以浪费掉kkksc03的一些时间的同时消耗掉kkksc03的一些金钱以满足自己的一个愿望. Kkks ...

  7. 动态规划(二)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 ...

  8. 动态规划二:最长公共子序列(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- ...

  9. 【学习笔记】动态规划—各种 DP 优化

    [学习笔记]动态规划-各种 DP 优化 [大前言] 个人认为贪心,\(dp\) 是最难的,每次遇到题完全不知道该怎么办,看了题解后又瞬间恍然大悟(TAT).这篇文章也是花了我差不多一个月时间才全部完成 ...

  10. (lintcode全部题目解答之)九章算法之算法班题目全解(附容易犯的错误)

    --------------------------------------------------------------- 本文使用方法:所有题目,只需要把标题输入lintcode就能找到.主要是 ...

随机推荐

  1. angular-gridster2使用

    1.安装:npm install angular-gridster2 --save 2.引入 3.html代码 <div id="fullscreen" style=&quo ...

  2. 为什么你需要升级 pip

    更新软件版本可以修复bug,增加新功能和提升性能.例如,NumPy 1.20 添加了类型注释,并在可能的情况下通过使用SIMD来提高性能.如果您要安装NumPy,则可能要安装最新版本. 相反,如果您使 ...

  3. Python pexpect 库的简单使用

    一.Python pexpect 库的使用 在终端中许多命令都有与用户交互的场景,例如切换用户时需要手动输入密码,安装应用有时要输入默认配置等.这对 shell 自动化脚本十分不便.expect 命令 ...

  4. applicationContext使用注解代替

    创建一个类SpringConfig @Configuration//证明这个类是spring的配置文件类 @ComponentScan("com.itheima")//扫描的哪些包 ...

  5. AD使用积累 - 板子上开孔的方法

    有时候画板子时需要需要在板子上开一些槽孔,可以参考如下两种方法: 第一种方法:封闭曲线转换为槽孔. 1.在机械层画一个封闭图形或者画一根线: 2.选中这个图形或线,选择工具 - 转换 - 以选中的元素 ...

  6. Otto Group Product Classification

    遇到的坑: 做多分类,用CrossEntropyLoss时,训练时候的正确标签的范围应该是[0,n-1],而不是[1,n],不然会报 IndexError: Target is out of boun ...

  7. 微信小程序页面间通的5种方式

    PageModel(页面模型)对小程序而言是很重要的一个概念,从app.json中也可以看到,小程序就是由一个个页面组成的. 如上图,这是一个常见结构的小程序:首页是一个双Tab框架PageA和Pag ...

  8. 微信小程序 入门总结篇

      页面生命周期 Page({ /** * 页面的初始数据 */ data: { }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { }, ...

  9. VisualVM无法运行,修改配置文件

      在VisualVM安装位置下找到etc目录修改etc目录下的visualvm.conf文件 加入配置 参数 指定JDK或JRE路径,如 visualvm_jdkhome="C:\xxx\ ...

  10. How to Change Reset Retrieve the WebLogic Server Administrator Password on WLS 10.3.6 or earlier

    To change the Administrator password on WLS 10.3.6 or earlier, perform the following steps depending ...