代码随想录算法训练营-Day34

题目

46. 携带研究材料

题解:

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

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 1. 读取输入
        if (!scanner.hasNextInt()) return;
        int M = scanner.nextInt(); // 物品的数量 (研究材料的种类)
        int N = scanner.nextInt(); // 背包的最大容量 (行李空间)
        
        int[] weight = new int[M];
        int[] value = new int[M];
        
        // 读取每个物品的重量 (占用空间)
        for (int i = 0; i < M; i++) {
            weight[i] = scanner.nextInt();
        }
        // 读取每个物品的价值
        for (int i = 0; i < M; i++) {
            value[i] = scanner.nextInt();
        }
        
        // 2. 定义二维 dp 数组
        // dp[i][j] 表示从 [0~i] 的物品中任意取,放进容量为 j 的背包,所能产生的最大价值
        int[][] dp = new int[M][N + 1];
        
        // 3. dp 数组初始化
        // 3.1 第一列 (j=0):背包容量为 0,最大价值必然为 0。(Java 默认数组为 0,无需额外写代码)
        // 3.2 第一行 (i=0):只考虑第 0 件物品时。
        // 只要背包容量 j 大于等于第 0 件物品的重量,就把它装进去,价值更新为 value[0]
        for (int j = weight[0]; j <= N; j++) {
            dp[0][j] = value[0];
        }
        
        // 4. 双重循环遍历,推导状态
        // 外层循环:遍历物品(因为 i=0 已经初始化过了,所以从 i=1 开始)
        for (int i = 1; i < M; i++) {
            // 内层循环:遍历背包容量(二维数组,容量正序遍历即可)
            for (int j = 0; j <= N; j++) {
                
                if (j < weight[i]) {
                    // 情况一:当前背包容量装不下第 i 件物品,只能不装
                    dp[i][j] = dp[i - 1][j];
                } else {
                    // 情况二:装得下。在"不装"和"装"之间取最大值
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
                }
                
            }
        }
        
        // 5. 打印结果
        // 右下角的元素:考虑了所有 M 件物品,且拥有最大容量 N 时的最大价值
        System.out.println(dp[M - 1][N]);
        
        scanner.close();
    }
}

滚动数组优化:

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

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 1. 读取输入
        if (!scanner.hasNextInt()) return;
        int M = scanner.nextInt(); // 物品的数量 (研究材料)
        int N = scanner.nextInt(); // 背包的最大容量 (行李空间)
        
        int[] weight = new int[M];
        int[] value = new int[M];
        
        for (int i = 0; i < M; i++) {
            weight[i] = scanner.nextInt();
        }
        for (int i = 0; i < M; i++) {
            value[i] = scanner.nextInt();
        }
        
        // 2. 定义一维 dp 数组
        // dp[j] 表示:容量为 j 的背包,所能装下的物品最大价值
        int[] dp = new int[N + 1];
        
        // 3. dp 数组初始化
        // Java 中 int 数组默认全为 0。
        // 背包容量为 0 时价值为 0,符合逻辑,无需额外初始化代码。
        
        // 4. 双重循环遍历,推导状态
        // 外层循环:遍历每一个物品 i
        for (int i = 0; i < M; i++) {
            
            // 内层循环:遍历背包容量 j
            // 【极其重要】:一维 01 背包,内层循环必须【倒序遍历】!
            // 并且遍历的下限是当前物品的重量 weight[i],因为容量小于它时根本装不下,dp[j] 保持原值即可
            for (int j = N; j >= weight[i]; j--) {
                
                // 状态转移方程:
                // dp[j] (新值) = max( dp[j] (其实是上一层的旧值,代表不拿当前物品), 
                //                     dp[j - weight[i]] (上一层的旧值,腾出空间) + value[i] (拿当前物品) )
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
                
            }
        }
        
        // 5. 打印结果
        System.out.println(dp[N]);
        
        scanner.close();
    }
}
416. 分割等和子集

题解:

java 复制代码
class Solution {
    public boolean canPartition(int[] nums) {
        int n = nums.length;
        
        // 1. 计算总和,判断是否能平分
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        // 如果总和是奇数,绝对不可能平分为两个整数子集
        if (sum % 2 != 0) {
            return false;
        }
        
        // 背包的容量就是总和的一半
        int target = sum / 2;
        
        // 2. 定义二维 dp 数组
        // dp[i][j] 表示从 [0, i] 个元素中挑选,能否刚好凑成和为 j
        boolean[][] dp = new boolean[n][target + 1];
        
        // 3. 初始化 dp 数组
        // 3.1 第一列 (j=0):目标和为 0,无论面对什么范围的数字,只要"一个都不选"就能凑出 0,所以都是 true
        for (int i = 0; i < n; i++) {
            dp[i][0] = true;
        }
        
        // 3.2 第一行 (i=0):只考虑第 0 个数字 nums[0]
        // 只有当目标和 j 恰好等于 nums[0] 时,才能凑成(前提是 nums[0] 不能超过 target)
        if (nums[0] <= target) {
            dp[0][nums[0]] = true;
        }
        
        // 4. 双重循环推导状态
        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= target; j++) {
                
                if (j < nums[i]) {
                    // 背包容量不够,装不下当前数字,只能继承不选当前数字的结果
                    dp[i][j] = dp[i - 1][j];
                } else {
                    // 背包容量足够,可以选也可以不选,只要有一种情况能凑出 j 即可
                    dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
                }
                
            }
        }
        
        // 5. 返回结果:看所有数字挑完后,能否刚好凑出 target
        return dp[n - 1][target];
    }
}

滚动数组优化:

java 复制代码
class Solution {
    public boolean canPartition(int[] nums) {
        // 1. 基础判断与求和
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        
        // 如果总和是奇数,绝对不可能平分为两个整数子集
        if (sum % 2 != 0) {
            return false;
        }
        
        // 我们的目标是凑出总和的一半
        int target = sum / 2;
        
        // 2. 定义一维 dp 数组
        // dp[j] 表示能否凑出总和为 j 的子集
        boolean[] dp = new boolean[target + 1];
        
        // 3. 初始化
        // 如果目标和是 0,一个数字都不选就能达到,所以为 true
        // 其他的 Java 默认初始化为 false
        dp[0] = true;
        
        // 4. 开始 01 背包的一维推导
        // 外层循环:遍历每一个数字(物品)
        for (int i = 0; i < nums.length; i++) {
            
            // 内层循环:遍历目标和(背包容量)
            // 【核心铁律】:必须从大到小倒序遍历!防止当前数字被重复使用
            // 遍历下限是 nums[i],因为如果 j < nums[i],根本装不下,dp[j] 保持原样即可
            for (int j = target; j >= nums[i]; j--) {
                
                // 状态转移方程:
                // 能否凑出 j = (不选当前数字就能凑出 j) 或者 (选了当前数字,且之前能凑出 j - nums[i])
                dp[j] = dp[j] || dp[j - nums[i]];
                
            }
            
            // 【一个小优化】:如果还没遍历完所有物品,就已经能凑出 target 了,提前结束
            if (dp[target]) {
                return true;
            }
        }
        
        // 5. 最终返回能否凑出 target
        return dp[target];
    }
}
相关推荐
Yzzz-F1 小时前
牛客寒假算法训练营2
算法
甄心爱学习2 小时前
【python】获取所有长度为 k 的二进制字符串
python·算法
iAkuya2 小时前
(leetcode)力扣100 76数据流的中位数(堆)
算法·leetcode·职场和发展
键盘鼓手苏苏2 小时前
Flutter for OpenHarmony: Flutter 三方库 ntp 精准同步鸿蒙设备系统时间(分布式协同授时利器)
android·分布式·算法·flutter·华为·中间件·harmonyos
董董灿是个攻城狮2 小时前
AI 视觉连载5:传统 CV 之均值滤波
算法
多恩Stone3 小时前
【3D-AICG 系列-11】Trellis 2 的 Shape VAE 训练流程梳理
人工智能·pytorch·算法·3d·aigc
lintax3 小时前
计算pi值-积分法
python·算法·计算π·积分法
你的冰西瓜4 小时前
C++ STL算法——排序和相关操作
开发语言·c++·算法·stl
今儿敲了吗4 小时前
29| 高考志愿
c++·笔记·学习·算法