动态规划 | part04

1049. 最后一块石头的重量 II - 力扣(LeetCode)

有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;

如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。

最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。

示例:

  • 输入:[2,7,4,1,8,1]
  • 输出:1

解释:

  • 组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
  • 组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
  • 组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
  • 组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。

提示:

  • 1 <= stones.length <= 30

  • 1 <= stones[i] <= 1000

    public int lastStoneWeightII(int[] stones) {
    if (stones.length == 1) {
    return stones[0];
    }
    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++) {
    //用大小为target的背包去装物品, j >= nums[i]是保证背包容量够用
    for (int j = target; j >= stones[i]; j--) {
    dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
    }
    }
    return sum - dp[target] - dp[target];
    }

解题:

类似于416. 分割等和子集 - 力扣(LeetCode),我们本题是去磨石头,每次拿俩块磨,所以我们与上面这题类似,要找到sum / 2,找到最大的一半,然后用sum - dp[target] - dp[target]去获取最后剩下的那个石头。这个石头就是剩下的最小的,因为我们已经尽可能的磨了,取的是和的一半,就算没有取满,但是剩下的依旧很小。

|----------|-------------------------------------------|
| 要点 | 说明 |
| 问题转化 | 石头碰撞 → 分成两堆使差最小 → 0/1背包 |
| 背包容量 | target = sum / 2(向下取整) |
| 倒序遍历 | j 从大到小,确保每个物品只用一次 |
| 状态定义 | dp[j] = 容量j的背包能装的最大重量 |
| 转移方程 | dp[j] = max(dp[j], dp[j-stone] + stone) |

494. 目标和 - 力扣(LeetCode)

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:

  • 输入:nums: [1, 1, 1, 1, 1], S: 3
  • 输出:5

解释:

  • -1+1+1+1+1 = 3
  • +1-1+1+1+1 = 3
  • +1+1-1+1+1 = 3
  • +1+1+1-1+1 = 3
  • +1+1+1+1-1 = 3

一共有5种方法让最终目标和为3。

提示:

  • 数组非空,且长度不会超过 20 。

  • 初始的数组的和不会超过 1000 。

  • 保证返回的最终结果能被 32 位整数存下。

    public int findTargetSumWays(int[] nums, int target) {
    int sum = 0;
    for (int num : nums) {
    sum += num;
    }

    复制代码
      // 边界条件1:目标和绝对值超过总和,不可能
      if (Math.abs(target) > sum) {
          return 0;
      }
      // 边界条件2:(target + sum) 必须是偶数,且 target 不能超过 sum
      if ((target + sum) % 2 != 0 || target > sum) {
          return 0;
      }
    
      // 转化为背包容量
      int left = (target + sum) / 2;
      
      // dp[j] 表示和为 j 的方案数
      int[] dp = new int[left + 1];
      dp[0] = 1;  // 和为 0 的方案有 1 种:什么都不选
      
      // 0/1 背包:遍历物品
      for (int i = 0; i < nums.length; i++) {
          // 倒序遍历容量(0/1背包防止重复计算)
          for (int j = left; j >= nums[i]; j--) {
              // 状态转移:不选 nums[i] 的方案数 + 选 nums[i] 的方案数
              dp[j] = dp[j] + dp[j - nums[i]];
          }
      }
      
      return dp[left];

    }

解题:

思路:分为正数集合和负数集合,俩个集合相加要等于目标值,这里记作left - right = target, 并且 left + right = sum, 所以left - (sum - left) = target,所以left = (target + sum)/ 2。这样我们就确定了我们要找的值,并且如果(target + sum)/ 2无法整除,就说明不能找到这样的集合,直接返回即可。

根据题目意思,我们的dp数组就是容量为i的背包有多少种填满方式,递推公式类似于机器人走到右下角的题目(不同路径) 。并且需要注意的是,我们的dp[0]是初始化为1,因为后续我们递推需要有值,而不是0,若是0的话后续都是0.

474. 一和零 - 力扣(LeetCode)

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。

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

示例 1:

  • 输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
  • 输出:4
  • 解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。 其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

示例 2:

  • 输入:strs = ["10", "0", "1"], m = 1, n = 1
  • 输出:2
  • 解释:最大的子集是 {"0", "1"} ,所以答案是 2 。

提示:

  • 1 <= strs.length <= 600

  • 1 <= strs[i].length <= 100

  • strs[i] 仅由 '0' 和 '1' 组成

  • 1 <= m, n <= 100

    public int findMaxForm(String[] strs, int m, int n) {
    int[][] dp = new int[m + 1][n + 1];
    for (String str : strs) {
    int[] count = new int[2];
    for (int i = 0; i < str.length(); i++) {
    if (str.charAt(i) == '0') {
    count[0]++;
    } else {
    count[1]++;
    }
    }
    for (int i = m; i >= count[0]; i--) {
    for (int j = n; j >= count[1]; j--) {
    dp[i][j] = Math.max(dp[i][j], dp[i - count[0]][j - count[1]] + 1);
    }
    }
    }
    return dp[m][n];
    }

解题:

字符串数组中,每一个元素是一个一个字符串,我们需要的是这个字符串中0和1的数量,所以后续我们需要统计每一个字符串的相关值。定义dp[i][j],表示分别最大存储 i 和 j 的数量,这里去表示0和1。我们的递推公式是类似于之前的一维递推dp[i][j] = Math.max(dp[i][j], dp[i - count[0]][j - count[1]] + 1),区别在于这里有俩个维度,所以计算的时候,俩个都要考虑。遍历顺序依旧是倒序遍历,保证元素只被使用一次。

相关推荐
追风少年ii2 小时前
顶刊分享--由细菌-癌细胞相互作用决定的差异性肿瘤免疫
人工智能·算法·数据分析·空间·单细胞
随意起个昵称2 小时前
Floyd算法做题笔记
笔记·算法
逆向菜鸟2 小时前
【原创】基因编辑公式总结及延缓衰老方法
算法
追随者永远是胜利者2 小时前
(LeetCode-Hot100)200. 岛屿数量
java·算法·leetcode·职场和发展·go
田里的水稻2 小时前
LPC_激光点云定位(LSLAM)-正态分布变换(NDT)
人工智能·算法·数学建模·机器人·自动驾驶
宇木灵2 小时前
C语言基础-八、结构体和共同(用)体
c语言·开发语言·数据结构·笔记·学习·算法
plus4s2 小时前
2月21日(91-93题)
c++·算法
陈天伟教授2 小时前
人工智能应用- 材料微观:03. 微观结构:纳米金
人工智能·神经网络·算法·机器学习·推荐算法
孞㐑¥3 小时前
算法—穷举,爆搜,深搜,回溯,剪枝
开发语言·c++·经验分享·笔记·算法