1049. 最后一块石头的重量 II
题目链接:1049. 最后一块石头的重量 II
文档讲解:代码随想录
状态:没想到尽量让石头分成重量相同的两堆
思路:
尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了,和昨天的相同子集问题就类似了。
题解:
public int lastStoneWeightII(int[] stones) {
// 计算所有石头的重量总和
int sum = 0;
for (int stone : stones) {
sum += stone;
}
// 目标是总重量的一半,因为我们希望两堆石头的重量尽量接近
int target = sum / 2;
// dp数组表示能达到的最接近target的重量
int[] dp = new int[target + 1];
// 遍历每个石头
for (int i = 0; i < stones.length; i++) {
// 从目标重量开始,向前更新dp数组
for (int j = target; j >= stones[i]; j--) {
// 更新dp[j],选择当前石头或不选择当前石头中的最大值
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
}
}
// 最后,返回两堆石头重量差的绝对值
return sum - 2 * dp[target];
}
494. 目标和
思路:
想要得到target,nums中有一些数字为正一些为负,不妨设为正的的数字总和为pos,为负的数字总和为neg,此时可以得到下面方程组:
s
u
m
=
p
o
s
+
n
e
g
t
a
r
g
e
t
=
p
o
s
−
n
e
g
\begin{align} sum & = pos + neg \\ target & = pos - neg \end{align}
sumtarget=pos+neg=pos−neg
那么,
pos
=
(
sum
+
target
)
2
或
neg
=
(
sum
−
target
)
2
\text{pos} = \frac{(\text{sum} + \text{target})}{2} \quad \text{或} \quad \text{neg} = \frac{(\text{sum} - \text{target})}{2}
pos=2(sum+target)或neg=2(sum−target)
此时这个问题就转换成了数组中的数字如何选取可以得到和为pos或者neg,因为数字的选取不重复,所以这是个01背包问题。
dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
有哪些来源可以推出dp[j]呢?
只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法。
例如:dp[j],j 为5,
已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
已经有一个3(nums[i]) 的话,有 dp[2]种方法 凑成 容量为5的背包
已经有一个4(nums[i]) 的话,有 dp[1]种方法 凑成 容量为5的背包
已经有一个5 (nums[i])的话,有 dp[0]种方法 凑成 容量为5的背包
那么凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来。
所以求组合类问题的公式,都是类似这种:
dp[j] += dp[j - nums[i]]
dp[0] = 1 表示有一种方式使和为0,即不选取任何元素。
背包容量0理解成什么都装不下就是0,理解成只有什么都不装就是1,这里要求的是装法,所以选择后者。
题解:
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
// 计算数组所有元素的和
for (int num : nums) {
sum += num;
}
// 检查是否存在解:sum - target 必须是偶数且 sum >= target
if ((sum - target) % 2 != 0 || sum < target) {
return 0;
}
// 计算子集和 size
int size = (sum - target) / 2;
int[] dp = new int[size + 1];
dp[0] = 1; // 有一种方式构成和为0
// 遍历每一个数字
for (int num : nums) {
// 从后往前遍历,更新dp数组
for (int j = size; j >= num; j--) {
dp[j] += dp[j - num];
}
}
return dp[size];
}
474.一和零
思路:
这道题属于二维0-1背包问题。
在一维的0-1背包中,dp[i] = Math.max(dp[i],dp[i - nums[i]] + nums[i] )
二维中,dp[i][j] = max(dp[i][j], dp[i - weight][j - volume] + value),本题中就是dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
二维0-1背包:背包有两个容量限制,每个物品有两个维度的消耗。每个物品只能放入一次。
二重背包:有两个独立的背包,每个背包有自己的容量限制。每个物品可以放入其中一个背包,也可以不放入。
题解:
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; // x 统计 '0' 的个数,y 统计 '1' 的个数
// 统计当前字符串中 '0' 和 '1' 的个数
for (char c : str.toCharArray()) {
if (c == '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];
}