代码随想录算法训练营 动态规划part05

一最后一块石头的重量 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]);
    }
}

二、目标和 

494. 目标和 - 力扣(LeetCode)

    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];
    }


三、一和零  

474. 一和零 - 力扣(LeetCode)

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];
    }
}

474. 一和零 - 力扣(LeetCode)

你可能感兴趣的:(动态规划,算法)