Day46~完全背包、518. 零钱兑换 II、377. 组合总和 Ⅳ

28 阅读5分钟

摘要

本文主要介绍了完全背包理论基础、以及LeetCode动态规划的两个题目,包括518. 零钱兑换 II和377. 组合总和 Ⅳ。

1、完全背包

1.1 题目描述

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次) ,求解将哪些物品装入背包里物品价值总和最大。

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件

1.2 思路

  • dp 数组以及下标的含义: 在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]

  • 递推公式

    • 第一种情况:放不下物品,0-i的物品, j容量可以放下的最大价值等于0-(i-1)的物品,j-1容量的价值

      • dp[j] = dp[j]
    • 第二种情况:可以放下物品,由dp[j - weight[i]]推出,dp[j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[j - weight[i]] + value[i](物品i的价值),就是背包放物品i得到的最大价值

      • dp[j] = Math.max(dp[j], value[i] + dp[j-weight[i]])
    • 如果 j < items[i].weight 则放不下物品,反之可以放下物品

  • dp 数组如何初始化

  • dp 数组遍历顺序:先遍历物品,然后遍历背包,背包是正序遍历

  • 打印 dp 数组

1、01背包和完全背包的区别?

01背包和完全背包唯一不同就是体现在遍历顺序上,都是先遍历物品,然后遍历背包,但是01背包遍历背包是倒序遍历,完全背包遍历背包是正序遍历

2、为什么完全背包是正序遍历?

正序遍历物品更适合完全背包问题,因为你需要考虑是否将当前物品添加到背包中,以获得最大化的总价值。正序遍历背包容量时,你可以利用之前计算得到的结果,根据状态转移方程来更新当前背包容量的值。

1.3 代码

public class Rucksack3 {
​
    public static void main(String[] args) {
        Item item1 = new Item(1, 15);
        Item item2 = new Item(3, 20);
        Item item3 = new Item(4, 30);
        Item[] items = {item1, item2, item3};
        int m = 4;
        rucksack(items, m);
    }
​
    // 滚动数组就是把上一层数组拷贝下来了
    // dp[j] 放入重量为 j 的背包的最大价值为 dp[j]
    // 不放物品 dp[j] = dp[j]
    // 放物品  dp[j] = Math.max(dp[j], items[i].value + dp[j-items[i].weight])
    // 如果j < j-items[i].weight] 则不取物品,反之可以取物品
    public static void rucksack(Item[] items, int m) {
        int n = items.length;
        int[] dp = new int[m + 1];
​
        // 先遍历物品,然后遍历背包,背包是正序遍历
        for (int i = 0; i < n; i++) {
            for (int j = 1; j <= m; j++) {
                if (j < items[i].weight) {
                    dp[j] = dp[j];
                } else {
                    dp[j] = Math.max(dp[j], items[i].value + dp[j - items[i].weight]);
                }
            }
        }
​
        // 打印
        print(dp, n, m);
    }
​
    public static void print(int[] dp, int n, int m) {
        for (int j = 0; j <= m; j++) {
            System.out.print(dp[j] + "\t");
        }
    }
​
    public static class Item {
​
        public int weight;
​
        public int value;
​
        public Item() {
        }
​
        public Item(int weight, int value) {
            this.weight = weight;
            this.value = value;
        }
    }
}

2、518. 零钱兑换 II

2.1 思路

动规五部曲

  • dp 数组以及下标的含义

    • dp[j] 表示可以凑成总金额 j 的方式为 dp[j]
  • 递推公式

    • dp[j] += dp[j - coins[i]]
    • 这个递推公式大家应该不陌生了,我在讲解01背包题目的时候在这篇494. 目标和中就讲解了,求装满背包有几种方法,公式都是:dp[j] += dp[j - nums[i]];
  • dp 数组如何初始化

    • 首先dp[0]一定要为1,dp[0] = 1是 递归公式的基础。
    • dp[0]=1还说明了一种情况:如果正好选了coins[i]后,也就是j-coins[i] == 0的情况表示这个硬币刚好能选,此时dp[0]为1表示只选coins[i]存在这样的一种选法。
  • dp 数组遍历顺序

    • 完全背包,物品和背包都是从小到大的遍历顺序
  • 举例推导dp数组

1、如何推导出递推公式:dp[j] += dp[j - coins[i]]

  • dp[j] 表示总金额为 j 时的组合方法数。
  • dp[j - coins[i]] 表示在不考虑当前硬币 coins[i] 的情况下,总金额为 j - coins[i] 时的组合方法数

2.2 代码

    public int change(int amount, int[] coins) {
        int[] dp = new int[amount+1];
        dp[0] = 1;
​
        for(int i=0; i<coins.length; i++) {
            for(int j=1; j<=amount; j++) {
                if(j >= coins[i]) {
                    dp[j] += dp[j-coins[i]];
                }
            }
        }
        return dp[amount];
    }

3、377. 组合总和 Ⅳ

3.1 思路

本题与动态规划:518.零钱兑换II就是一个鲜明的对比,一个是求排列,一个是求组合,遍历顺序完全不同。

  • 如果求组合数就是外层for循环遍历物品,内层for遍历背包
  • 如果求排列数就是外层for遍历背包,内层for循环遍历物品

1、什么是组合和排列?

本题题目描述说是求组合,但又说是可以元素相同顺序不同的组合算两个组合,其实就是求排列!

组合不强调顺序,(1,5)和(5,1)是同一个组合。

排列强调顺序,(1,5)和(5,1)是两个不同的排列

2、为什么求排列数就是外层for遍历背包,内层for循环遍历物品?

在完全背包问题中,每种物品可以被选择多次,因此考虑不同顺序的选择是合理的。如果你希望计算排列数,那么你需要考虑不同物品的选择顺序,这就需要在内层循环中遍历不同的物品。

3.2 代码

    public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target+1];
        dp[0] = 1;
​
        for(int j=1; j<=target; j++) {
            for(int i=0; i<nums.length; i++) {
                if(j >= nums[i]) {
                    dp[j] += dp[j-nums[i]];
                }
            }
        }
        return dp[target];
    }

参考资料

代码随想录-完全背包

代码随想录-518. 零钱兑换 II

代码随想录-377. 组合总和 Ⅳ