一个人的朝圣 — LeetCode打卡第43天
知识总结
今天学习背包问题以及其应用, 重点掌握其思想以及如何将其他问题同背包问题类比起来
Leetcode 背包问题
题目说明
经典的背包, 给定指定的物品的容量和价值, 求在指定的背包容量下可以装物品的最大价值.
代码说明
一些需要记住的套路:
- 二维数组代表当前物品i, 背包容量j下可以装的最大价值.
- 一维数组是指一个滚动数组, 遍历顺序固定了, 先遍历物品, 再遍历容量, 并且需要倒序遍历.
- 递推公式:
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
package com.backpack;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
public class backpack {
int[] weight = {
1, 3, 4, 2};
int[] value = {
15, 27, 30, 20};
int bagSize = 5;
@Test
void test2Dbackpack() {
int num = weight.length;
int[][] dp = new int[num][bagSize + 1];
// initialize the first row
for (int j = 0; j <= bagSize; j++) {
// when the first item weight is larger or equal than
// the current bag size, we can put the item into the bag
if (weight[0] <= j) dp[0][j] = value[0];
}
for (int i = 1; i < num; i++) {
//遍历物品
for (int j = 1; j <= bagSize; j++) {
// 遍历背包
if (j - weight[i] < 0) {
dp[i][j] = dp[i - 1][j]; // 当前容量不能放得下物品i
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
}
for (int i = 0; i < num; i++) {
System.out.println(Arrays.toString(dp[i]));
}
System.out.println("Max value is " + dp[num - 1][bagSize]);
}
@Test
void test1Dbackpack() {
int[] dp = new int[bagSize + 1];
dp[0] = 0;
for (int i = 0; i < weight.length; i++) {
for (int j = bagSize; j >= 0; j--) {
if (j >= weight[i]) {
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
System.out.println(Arrays.toString(dp));
}
System.out.println("Max value is " + dp[bagSize]);
}
}
Leetcode 416. 分割等和子集
题目说明
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
代码说明
将问题转化成为: 能否在容量为sum/2 的背包中装满价值为sum/ 2的物品, 如果可以则能等分, 否则不能.
此处物品的体积和价值相等
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for(int i = 0; i < nums.length; i++){
sum += nums[i];
}
if(sum % 2 == 1) return false;
int target = sum / 2;
int[] dp = new int[target+1];
dp[0] = 0;
for(int i = 0; i < nums.length; i++){
for(int j = target; j >= nums[i]; j--){
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
// System.out.println(Arrays.toString(dp));
}
return dp[target] == target;
}
}
Leetcode 1049. 最后一块石头的重量 II
题目说明
有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。
代码说明
这道题和前面会比较类似, 如果说可以等分的, 那么就返回0, 但是如果不能等分的话, 怎么办呢?
将石头分为两组, 两组的的差值要尽可能的小. 假设总的为23, 那么target = 11, 另外一边就为12. 容量为11的背包里可以装的最大价值为: dp[target] <= 11, 那么两个背包的价值差 = (sum - dp[target]) - dp[targert]
class Solution {
public int lastStoneWeightII(int[] stones) {
int sum = 0;
for(int i = 0; i < stones.length; i ++){
sum += stones[i];
}
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]);
}
// System.out.println(Arrays.toString(dp));
}
return sum - 2 * dp[target];
}
}
Leetcode 494. 目标和
题目说明
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
代码说明
这里有一个很巧妙的转化, target 可以当做 positive - negative组,
positive + negative = sum, 所有positive = (sum + target) / 2
问题就转化成装满容量为positive的背包有多少种方式.
需要注意的是target如果为负数时需要转成正数进行运算.
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for(int num : nums){
sum += num;
}
if(target < 0) target = -target;
int total = sum += target;
if(total % 2 != 0) return 0;
int bagSize = Math.abs(total / 2);
int[] dp = new int[bagSize+1];
dp[0] = 1;
for(int i = 0; i < nums.length; i++){
for(int j = bagSize; j>= nums[i]; j--){
dp[j] += dp[j - nums[i]];
}
// System.out.println(Arrays.toString(dp));
}
return dp[bagSize];
}
}
Leetcode 474. 一和零
题目说明
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。
代码说明
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m+1][n+1];
for(String str : strs){
int x = 0, y = 0;
for(char ch : str.toCharArray()){
if(ch == '0'){
x++;
}else{
y++;
}
}
for(int i = m; i >= x; i--){
for(int j = n; j >= y; j--){
dp[i][j] = Math.max(dp[i][j], dp[i-x][j-y] + 1);
}
}
}
return dp[m][n];
}
}