代码随想录训练营第三十五天 | 01背包问题 二维 01背包问题 一维 416. 分割等和子集

01背包问题 二维

一开始以为一维dp数组即可,但是在递推时,发现需要每个已知量都需要对应的used数组,来判断装入了哪些东西。

相比于之前只是草草看了一遍,这次动手做发现很多点真不容易想到。

import java.util.*;

class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int m = sc.nextInt(); // 种类数
        int n = sc.nextInt(); // 行李空间
        int[][] arr = new int[2][m];
        for(int i=0; i<m; i++){ // 空间
            arr[0][i] = sc.nextInt();
        }
        for(int i=0; i<m; i++){ // 价值
            arr[1][i] = sc.nextInt();
        }

        int[][] dp = new int[m+1][n+1]; // [i][k]: 已有的前i种种类,在面对k个行李空间时

        // 新加入一种种类,只有放入和不放入两种结果。比较两种结果即可。
        // 如何判断能否加入该种类?就要看 此时的空间-该物品的空间,这个空间的最大能装多少东西了 所以dp[i][k]递推需要dp[i][k-空间]
        for(int i=1; i<m+1; i++){ // 固定的种类数 i
            for(int k=1; k<n+1; k++){ // 不固定的行李空间 k
                int odd = k-arr[0][i-1]; // 此时的空间能否装下该物品(单独装下该物品)
                if(odd>=0) dp[i][k] = Math.max(dp[i-1][k], dp[i-1][odd] + arr[1][i-1]);
                else dp[i][k] = dp[i-1][k];
            }
        }
        System.out.println(dp[m][n]);
    }
}

01背包问题 一维

使用滚动数组,压缩空间——为了使用到前面的数,必须反向遍历。

import java.util.*;

class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int m = sc.nextInt(); // 种类数
        int n = sc.nextInt(); // 行李空间
        int[][] arr = new int[2][m];
        for(int i=0; i<m; i++){ // 空间
            arr[0][i] = sc.nextInt();
        }
        for(int i=0; i<m; i++){ // 价值
            arr[1][i] = sc.nextInt();
        }

        int[] dp = new int[n+1]; // 需要先固定种类,再固定行李空间

        for(int i=0; i<m; i++){
            for(int k=n; k>=0; k--){
                if(k>=arr[0][i]) dp[k] = Math.max(dp[k], dp[k-arr[0][i]] + arr[1][i]);
            }
        }
        System.out.println(dp[n]);
    }
}

代码随想录版本:

  1. 剪枝:遍历到该物品的空间大小以内时,不再遍历。
  2. 不可以先遍历背包容量嵌套遍历物品。因为根据二维dp以及其递推公式,发现背包一定要反向遍历。而只有在最内层才能反向遍历。
    当然,也正因此,可以实现剪枝。因为之前的都不会变啊,一定不会装下该物品的。
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取 M 和 N
        int M = scanner.nextInt();  // 研究材料的数量
        int N = scanner.nextInt();  // 行李空间的大小

        int[] costs = new int[M];   // 每种材料的空间占用
        int[] values = new int[M];  // 每种材料的价值

        // 输入每种材料的空间占用
        for (int i = 0; i < M; i++) {
            costs[i] = scanner.nextInt();
        }

        // 输入每种材料的价值
        for (int j = 0; j < M; j++) {
            values[j] = scanner.nextInt();
        }

        // 创建一个动态规划数组 dp,初始值为 0
        int[] dp = new int[N + 1];

        // 外层循环遍历每个类型的研究材料
        for (int i = 0; i < M; i++) {
            // 内层循环从 N 空间逐渐减少到当前研究材料所占空间
            for (int j = N; j >= costs[i]; j--) {
                // 考虑当前研究材料选择和不选择的情况,选择最大值
                dp[j] = Math.max(dp[j], dp[j - costs[i]] + values[i]);
            }
        }

        // 输出 dp[N],即在给定 N 行李空间可以携带的研究材料的最大价值
        System.out.println(dp[N]);

        scanner.close();
    }
}

416. 分割等和子集 (难想)

一眼看不出来要动规,所以想出这种解法,只能对一部分。

class Solution {
    public boolean canPartition(int[] nums) {
        Arrays.sort(nums);
        int a=0, b=0;
        for(int cur = nums.length-1; cur>=0; cur--){
            if(a<b) a += nums[cur];
            else b += nums[cur];
        }
        return a==b;
    }
}

本题的本质是,能否把容量为 sum / 2的背包装满。
既有一个 只能装重量为 sum / 2 的背包,商品为数字,这些数字能不能把 这个背包装满。
那每一件商品是数字的话,对应的重量 和 价值是多少呢?
一个数字只有一个维度,即 重量等于价值。
当数字 可以装满 承载重量为 sum / 2 的背包的背包时,这个背包的价值也是 sum / 2。

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = Arrays.stream(nums).sum();
        if (sum % 2 != 0) {
            return false;
        }
        int target = sum / 2; // 容量
        int[] dp = new int[target + 1]; // !!!此处为容量+1

        for (int i = 0; i < nums.length; i++) {
            for (int k = target; k >= nums[i]; k--) { // 容量小于物品体积时,停止循环
                dp[k] = Math.max(dp[k], dp[k - nums[i]] + nums[i]); // 不需要限制条件,因为k-nums[i]一定会>=0,是循环条件
            }
        }
        return dp[target] == target;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值