一最后一块石头的重量 II
1049. 最后一块石头的重量 II - 力扣(LeetCode)
假设想要得到最优解,我们需要按照如下顺序操作石子:[(sa,sb),(sc,sd),...,(si,sj),(sp,sq)]。
其中 abcdijpq 代表了石子编号,字母顺序不代表编号的大小关系。
如果不考虑「有放回」的操作的话,我们可以划分为两个石子堆(正号堆/负号堆):
将每次操作中「重量较大」的石子放到「正号堆」,代表在这次操作中该石子重量在「最终运算结果」中应用 + 运算符
将每次操作中「重量较少/相等」的石子放到「负号堆」,代表在这次操作中该石子重量在「最终运算结果」中应用 −-− 运算符
这意味我们最终得到的结果,可以为原来 stones 数组中的数字添加 +/− 符号,所形成的「计算表达式」所表示。
其实所谓的「有放回」操作,只是触发调整「某个原有石子」所在「哪个堆」中,并不会真正意义上的产生「新的石子重量」。
什么意思呢?
假设有起始石子 a 和 b,且两者重量关系为 a≥b,那么首先会将 a 放入「正号堆」,将 b 放入「负号堆」。重放回操作可以看作产生一个新的重量为 a−b 的"虚拟石子",将来这个"虚拟石子"也会参与某次合并操作,也会被添加 +/−符号:
当对"虚拟石子"添加 + 符号,即可 +(a−b),展开后为 a−b,即起始石子 a 和 b 所在「石子堆」不变
当对"虚拟石子"添加 − 符号,即可 −(a−b),展开后为 b−a,即起始石子 a 和 b 所在「石子堆」交换
因此所谓不断「合并」&「重放」,本质只是在构造一个折叠的计算表达式,最终都能展开扁平化为非折叠的计算表达式。
综上,即使是包含「有放回」操作,最终的结果仍然可以使用「为原来 stones 数组中的数字添加 +/− 符号,形成的"计算表达式"」所表示。
1049. 最后一块石头的重量 II - 力扣(LeetCode)
class Solution {
public int lastStoneWeightII(int[] ss) {
int n = ss.length;
int sum = 0;
for (int i : ss) sum += i;
int t = sum / 2;
int[][] f = new int[n + 1][t + 1];
for (int i = 1; i <= n; i++) {
int x = ss[i - 1];
for (int j = 0; j <= t; j++) {
f[i][j] = f[i - 1][j];
if (j >= x) f[i][j] = Math.max(f[i][j], f[i - 1][j - x] + x);
}
}
return Math.abs(sum - f[n][t] - f[n][t]);
}
}
二、目标和
public static int findTargetSumWays(int[] nums, int s) {
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
// 绝对值范围超过了sum的绝对值范围则无法得到
if (Math.abs(s) > Math.abs(sum)) return 0;
int len = nums.length;
// - 0 +
int t = sum * 2 + 1;
int[][] dp = new int[len][t];
// 初始化
if (nums[0] == 0) {
dp[0][sum] = 2;
} else {
dp[0][sum + nums[0]] = 1;
dp[0][sum - nums[0]] = 1;
}
for (int i = 1; i < len; i++) {
for (int j = 0; j < t; j++) {
// 边界
int l = (j - nums[i]) >= 0 ? j - nums[i] : 0;
int r = (j + nums[i]) < t ? j + nums[i] : 0;
dp[i][j] = dp[i - 1][l] + dp[i - 1][r];
}
}
return dp[len - 1][sum + s];
}
三、一和零
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int len = strs.length;
// 预处理每一个字符包含 0 和 1 的数量
int[][] cnt = new int[len][2];
for (int i = 0; i < len; i++) {
String str = strs[i];
int zero = 0, one = 0;
for (char c : str.toCharArray()) {
if (c == '0') {
zero++;
} else {
one++;
}
}
cnt[i] = new int[]{zero, one};
}
// 处理只考虑第一件物品的情况
int[][][] f = new int[len][m + 1][n + 1];
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
f[0][i][j] = (i >= cnt[0][0] && j >= cnt[0][1]) ? 1 : 0;
}
}
// 处理考虑其余物品的情况
for (int k = 1; k < len; k++) {
int zero = cnt[k][0], one = cnt[k][1];
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
// 不选择第 k 件物品
int a = f[k-1][i][j];
// 选择第 k 件物品(前提是有足够的 m 和 n 额度可使用)
int b = (i >= zero && j >= one) ? f[k-1][i-zero][j-one] + 1 : 0;
f[k][i][j] = Math.max(a, b);
}
}
}
return f[len-1][m][n];
}
}