Leetcode+Java+动态规划IV

494.目标和

给你一个非负整数数组 nums 和一个整数 target

向数组中的每个整数前添加 '+''-' ,然后串联起所有整数,可以构造一个 表达式

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1"

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

复制代码
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 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
+1 + 1 + 1 + 1 - 1 = 3

示例 2:

复制代码
输入:nums = [1], target = 1
输出:1

提示:

  • 1 <= nums.length <= 20
  • 0 <= nums[i] <= 1000
  • 0 <= sum(nums[i]) <= 1000
  • -1000 <= target <= 1000

原理

  • 问题分解
    • 转化为 0-1 背包问题:
      • 物品为 nums[i],重量和价值均为 nums[i]。
      • 背包容量为 bagSize = (sum + target) / 2。
      • 目标是计算和为 bagSize 的子集方案数。
    • 定义 dp[j] 为和为 j 的子集方案数。
    • 状态转移:
      • 对于每个数字 nums[i]:
        • 不选:dp[j] 不变。
        • 选(需 j >= nums[i]):dp[j] += dp[j - nums[i]](累加方案数)。
    • 使用一维数组,从大到小遍历 j,确保 dp[j - nums[i]] 使用上一轮状态。
  • 初始化
    • dp[0] = 1:空子集和为 0,有 1 种方案。
    • 其他 dp[j] = 0(默认)。
  • 边界条件
    • 如果 |target| > sum,无解,返回 0。
    • 如果 (sum + target) % 2 == 1,无整数解,返回 0。
    • 如果 bagSize < 0,无解(由 |target| <= sum 保证)。
  • 时间和空间复杂度
    • 时间复杂度:O(n * bagSize),n 为数组长度,bagSize <= (sum + target) / 2 <= 2000。
    • 空间复杂度:O(bagSize),使用一维 DP 数组。

代码

java 复制代码
class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        // 计算总和
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        
        // 检查边界条件
        if (Math.abs(target) > sum || (sum + target) % 2 == 1) {
            return 0;
        }
        
        // 计算背包容量
        int bagSize = (sum + target) / 2;
        
        // 创建 DP 数组
        int[] dp = new int[bagSize + 1];
        dp[0] = 1;
        
        // 遍历每个数字
        for (int num : nums) {
            if (num > bagSize) continue; // 跳过无效数字
            for (int j = bagSize; j >= num; j--) {
                dp[j] += dp[j - num];
            }
        }
        
        return dp[bagSize];
    }
}

474.一和零

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

请你找出并返回 strs 的最大子集的长度,该子集中 最多m0n1

如果 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

原理

  • 问题分解
    • 定义 dp[j][k] 为在最多 j 个 0 和 k 个 1 的限制下,能选择的最大字符串数量(子集长度)。
    • 对于每个字符串(有 zeros 个 0 和 ones 个 1):
      • 不选:dp[j][k] 不变。
      • 选(需 j >= zeros 且 k >= ones):dp[j][k] = max(dp[j][k], dp[j - zeros][k - ones] + 1)。
    • 状态转移:
      • dp[j][k] = max(dp[j][k], dp[j - zeros][k - ones] + 1)。
  • 初始化
    • dp[0][0] = 0(无 0 和 1 时,子集长度为 0)。
    • 其他 dp[j][k] 默认初始化为 0(Java 数组默认值)。
  • 遍历顺序
    • 使用二维 DP 数组,需从大到小遍历 j 和 k,以避免覆盖上一轮的状态(类似一维 0-1 背包的逆序遍历)。
    • 每个字符串的 zeros 和 ones 通过遍历字符计算。
  • 时间和空间复杂度
    • 时间复杂度 :O(L + N * m * n),其中:
      • L 是 strs 中所有字符串的总长度(计算 zeros 和 ones,L <= 600 * 100 = 6 * 10^4)。
      • N 是字符串数量(N <= 600),m * n 是 DP 数组遍历的次数(m, n <= 100)。
    • 空间复杂度:O(m * n),使用 m+1 * n+1 的 DP 数组。

代码

java 复制代码
class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        // 创建二维 DP 数组
        int[][] dp = new int[m + 1][n + 1];
        
        // 预计算每个字符串的 0 和 1 数量
        int[] zeros = new int[strs.length];
        int[] ones = new int[strs.length];
        for (int i = 0; i < strs.length; i++) {
            for (char c : strs[i].toCharArray()) {
                if (c == '0') zeros[i]++;
                else ones[i]++;
            }
        }
        
        // 遍历每个字符串
        for (int i = 0; i < strs.length; i++) {
            if (zeros[i] > m || ones[i] > n) continue;
            // 从大到小遍历
            for (int j = m; j >= zeros[i]; j--) {
                for (int k = n; k >= ones[i]; k--) {
                    dp[j][k] = Math.max(dp[j][k], dp[j - zeros[i]][k - ones[i]] + 1);
                }
            }
        }
        
        return dp[m][n];
    }
}

完全背包理论基础

题目描述

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的重量,并且具有不同的价值。

小明的行李箱所能承担的总重量是有限的,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料可以选择无数次,并且可以重复选择。

输入描述

第一行包含两个整数,n,v,分别表示研究材料的种类和行李所能承担的总重量

接下来包含 n 行,每行两个整数 wi 和 vi,代表第 i 种研究材料的重量和价值

输出描述

输出一个整数,表示最大价值。

输入示例

4 5

1 2

2 4

3 4

4 5

输出示例

10

提示信息

第一种材料选择五次,可以达到最大值。

数据范围:

1 <= n <= 10000;

1 <= v <= 10000;

1 <= wi, vi <= 10^9.

原理

  • 问题分解
    • 定义 dp[i][j] 为前 i 种物品(索引 0 到 i-1)在容量 j 下的最大价值。
    • 状态转移:
      • 不选第 i-1 种物品:dp[i][j] = dp[i - 1][j]。
      • 选第 i-1 种物品(可多次):dp[i][j] = max(dp[i][j], dp[i][j - weight[i-1]] + value[i-1])。
    • 合并为:dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i-1]] + value[i-1])(if j >= weight[i-1])。
  • 初始化
    • dp[0][j] = k * value[0],其中 k = j / weight[0](第一种物品可重复选择)。
    • dp[i][0] = 0(容量为 0,价值为 0)。
  • 一维 DP 优化
    • 由于每种物品可重复选择,可以用一维数组 dp[j],表示容量 j 下的最大价值。
    • 状态转移:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])(if j >= weight[i])。
    • 遍历 j 从小到大(与 0-1 背包相反),因为需要使用当前物品的最新状态。
  • 时间和空间复杂度
    • 二维 DP
      • 时间复杂度:O(n * v),遍历 n 种物品和 v 个容量。
      • 空间复杂度:O(n * v)。
    • 一维 DP
      • 时间复杂度:O(n * v)。
      • 空间复杂度:O(v)。

代码

java 复制代码
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 读取种类 n 和容量 v
        int n = scanner.nextInt();
        int v = scanner.nextInt();

        // 创建数组存储重量和价值
        int[] weight = new int[n];
        long[] value = new long[n]; // 使用 long 存储价值

        // 读取输入
        for (int i = 0; i < n; i++) {
            weight[i] = scanner.nextInt();
            value[i] = scanner.nextLong();
        }

        // 创建二维 DP 数组
        long[][] dp = new long[n][v + 1];

        // 初始化第一种物品
        for (int j = 0; j <= v; j++) {
            dp[0][j] = (j / weight[0]) * value[0]; // 尽量多次选择第一种物品
        }

        // 动态规划
        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= v; j++) {
                // 不选第 i 种物品
                dp[i][j] = dp[i - 1][j];
                // 选第 i 种物品(可多次)
                if (j >= weight[i]) {
                    dp[i][j] = Math.max(dp[i][j], dp[i][j - weight[i]] + value[i]);
                }
            }
        }

        System.out.println(dp[n - 1][v]);
        scanner.close();
    }
}

518.零钱兑换II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 1:

复制代码
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

复制代码
输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

示例 3:

复制代码
输入:amount = 10, coins = [10] 
输出:1

提示:

  • 1 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • coins 中的所有值 互不相同
  • 0 <= amount <= 5000

原理

  • 问题分解
    • 定义 dp[j] 为凑成金额 j 的组合数。
    • 状态转移:
      • 对于每种硬币 coins[i]:
        • 不使用该硬币:dp[j] 不变。
        • 使用该硬币(需 j >= coins[i]):dp[j] += dp[j - coins[i]](累加组合数)。
    • 遍历 j 从小到大(完全背包特性),因为需要使用当前硬币的最新状态。
  • 初始化
    • dp[0] = 1:金额 0 有 1 种组合(空组合)。
    • 其他 dp[j] = 0(默认)。
  • 时间和空间复杂度
    • 时间复杂度:O(n * amount),遍历 n 种硬币,每种硬币检查最多 amount 个金额。
    • 空间复杂度:O(amount),使用一维 DP 数组。
  • 数据范围
    • 1 <= coins.length <= 300
    • 1 <= coins[i] <= 5000
    • 0 <= amount <= 5000
    • 结果符合 32 位带符号整数(2^31 - 1 ≈ 2.1 * 10^9)。

代码

java 复制代码
class Solution {
    public int change(int amount, int[] coins) {
        // 创建一维 DP 数组
        int[] dp = new int[amount + 1];
        
        // 初始化:金额 0 有 1 种组合
        dp[0] = 1;
        
        // 遍历每种硬币
        for (int coin : coins) {
            // 从 coin 开始遍历金额
            for (int j = coin; j <= amount; j++) {
                dp[j] += dp[j - coin];
            }
        }
        
        return dp[amount];
    }
}
相关推荐
做科研的周师兄4 小时前
【机器学习入门】3.3 FP树算法——高效挖掘频繁项集的“树状神器”
java·大数据·数据库·人工智能·深度学习·算法·机器学习
上等猿4 小时前
JUC多线程个人笔记
android·java·笔记
yfs10244 小时前
钉钉补卡事件处理方案
java·数据库·钉钉
大飞哥~BigFei4 小时前
BigDecimal账户分布式原子操作
java·分布式
编啊编程啊程6 小时前
响应式编程框架Reactor【4】
java·开发语言
YoungHong199211 小时前
面试经典150题[019]:最后一个单词的长度(LeetCode 58)
leetcode·面试·职场和发展
Funcy13 小时前
XxlJob源码分析01:环境准备
java
the beard13 小时前
Feign整合Sentinel实现服务降级与Feign拦截器实战指南
java·spring·sentinel
THMAIL13 小时前
攻克 Java 分布式难题:并发模型优化与分布式事务处理实战指南
java·开发语言·分布式