代码随想录二刷第十三天 | 0-1背包专题

73 阅读4分钟

46. 携带研究材料

题目:46. 携带研究材料(第六期模拟笔试)

题解:代码随想录

状态:需要多复习

思路

0-1背包问题:

dp[i][j]:i表示物品种类,j表示背包重量

或者 dp[j]:j表示背包重量

代码(二维dp数组)

时间复杂度:O(M*N) 空间复杂度:O(M*N)

import java.util.*;

public class Main{
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        int M = in.nextInt();
        int N = in.nextInt();
        int[] space = new int[M];
        int[] value = new int[M];
        for(int i = 0; i < M; i++){
            space[i] = in.nextInt();
        }
        for(int i = 0; i < M; i++){
            value[i] = in.nextInt();
        }

        int[][] dp = new int[M][N + 1];
        for(int i = space[0]; i <= N; i++){
            dp[0][i] = value[0];
        }
        for(int i = 1; i < M; i++){
            for(int j = 1; j <= N; j++){
                if(space[i] > j) dp[i][j] = dp[i - 1][j];
                else dp[i][j] = Math.max(
                    dp[i - 1][j], 
                    dp[i - 1][j - space[i]] + value[i]
                );
            }
        }
        System.out.println(dp[M - 1][N]);
    }
}

代码(一维dp数组)

时间复杂度:O(N) 空间复杂度:O(N)

import java.util.*;

public class Main{
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        int M = in.nextInt();
        int N = in.nextInt();
        int[] space = new int[M];
        int[] value = new int[M];
        for(int i = 0; i < M; i++){
            space[i] = in.nextInt();
        }
        for(int i = 0; i < M; i++){
            value[i] = in.nextInt();
        }

        int[] dp = new int[N + 1];

        for(int i = 0; i < M; i++){
            for(int j = N; j >= space[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - space[i]] + value[i]);
            }
        }
        System.out.println(dp[N]);
    }

416. 分割等和子集

题目:416. 分割等和子集 - 力扣(LeetCode)

题解:代码随想录

状态:AC

思路

  • 先统计数组总和,若总和非偶数,则一定无法分割
  • 之后就是0-1背包问题,不过是要判断背包是否装满
  • 其中要加上一个剪枝操作if(dp[sum] == sum) return true; ,提早结束循环

代码

时间复杂度:O(n*sum) 空间复杂度:O(sum)

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for(int num : nums){
            sum += num;
        }
        if(sum % 2 != 0) return false;
        sum /= 2;
        int[] dp = new int[sum + 1];
        for(int i = 0; i < nums.length; i++){
            for(int j = sum; j >= nums[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }
            // 剪枝操作
            if(dp[sum] == sum) return true; 
        }
        return dp[sum] == sum;
    }
}

1049.最后一块石头的重量II

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

题解:代码随想录

状态:思路需要看一下

思路

416. 分割等和子集的变体,尽可能将石头分成重量相等的两组

代码

时间复杂度:O(n*sum) 空间复杂度:O(sum)

class Solution {
    public int lastStoneWeightII(int[] stones) {
        int sum = 0;
        for(int stone : stones){
            sum += stone;
        }
        int target = sum / 2;
        int[] dp = new int[target + 1];
        for(int i = 0; i < stones.length; i++){
            for(int j = target; j >= stones[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
            }
        }
        return sum - 2 * dp[target];
    }
}

494.目标和

题目:494. 目标和 - 力扣(LeetCode)

题解:代码随想录

状态:需要多复习

思路

从计算最大容量,改为计算填满背包的可能数

  • 注意(target + sum) % 2 == 1时,无解
  • 注意Math.abs(target) > sum时,无解

例如:dp[j],j 为5,

  • 已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
  • 已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
  • 已经有一个3(nums[i]) 的话,有 dp[2]种方法 凑成 容量为5的背包
  • 已经有一个4(nums[i]) 的话,有 dp[1]种方法 凑成 容量为5的背包
  • 已经有一个5 (nums[i])的话,有 dp[0]种方法 凑成 容量为5的背包

那么凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来。

代码

时间复杂度:O(N*size) 空间复杂度:O(size)

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        for(int num : nums){
            sum += num;
        }
        if((target + sum) % 2 == 1) return 0;
        if(Math.abs(target) > sum) return 0;
        int size = (sum + target) / 2;
        int[] dp = new int[size + 1];
        dp[0] = 1;
        for(int i = 0; i < nums.length; i++){
            for(int j = size; j >= nums[i]; j--){
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[size];
    }
}

474.一和零

题目:474. 一和零 - 力扣(LeetCode)

题解:代码随想录

状态:完全没思路

思路

  • dp数组定义:dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]
  • 递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
  • dp数组初始化:01背包的dp数组初始化为0
  • 遍历顺序:内外曾均为倒序

代码

时间复杂度:O() 空间复杂度:O()

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
         //dp[i][j]表示i个0和j个1时的最大子集
        int[][] dp = new int[m + 1][n + 1];
        int oneNum, zeroNum;
        for (String str : strs) {
            oneNum = 0;
            zeroNum = 0;
            for (char ch : str.toCharArray()) {
                if (ch == '0') {
                    zeroNum++;
                } else {
                    oneNum++;
                }
            }
            //倒序遍历
            for (int i = m; i >= zeroNum; i--) {
                for (int j = n; j >= oneNum; j--) {
                    dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
                }
            }
        }
        return dp[m][n];
    }
}