代码随想录算法训练营第四十四天 | 完全背包、518. 零钱兑换 II、377. 组合总和 Ⅳ

打卡第43天,去广州浪了几天,补卡补卡。

今日任务

  • 完全背包
    1. 零钱兑换 II
    1. 组合总和 Ⅳ

完全背包

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

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

例子:

背包最大重量为4。

物品为:

物品 重量 价值
物品0 1 15
物品1 3 20
物品2 4 30

每件商品都有无限个!

问背包能背的物品最大价值是多少?

  1. dp以及下标的定义
    dp[i][j] 背包重量为 j 最大价值。
  2. 递推公式
    d p [ i ] [ j ] = m a x ( m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) , d p [ i ] [ j − v [ i ] ] + w [ i ] ) dp[i][j] = max(max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]), dp[i][j - v[i]] + w[i]) dp[i][j]=max(max(dp[i1][j],dp[i1][jv[i]]+w[i]),dp[i][jv[i]]+w[i])
  3. 初始化
    dp[i][0] = 0; 重量为0,没有价值
    dp[0][j]

我的题解

#include<bits/stdc++.h>

using namespace std;

int main() {
    
    
    int N, V;
    scanf("%d%d", &N, &V);
    vector<int> v(N);
    vector<int> w(N);
    for(int i = 0; i < N; i++) {
    
    
        scanf("%d %d", &v[i], &w[i]);
    }
    
    vector<vector<int>> dp(N, vector<int> (V + 1, 0));
    for(int i = v[0]; i <= V; i++) dp[0][i] = dp[0][i - v[0]] + w[0];
    
    for(int i = 1; i < N; i++)
        for(int j = 1; j <= V; j++) {
    
    
            if(v[i] > j) dp[i][j] = dp[i - 1][j];
            else dp[i][j] = max(max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]), dp[i][j - v[i]] + w[i]);
        }
    
    // for(int i = 0 ; i < N; i++) {
    
    
    //     for(int j = 0; j <= V; j++) printf("%d ", dp[i][j]);
    //     printf("\n");
    // }
        
    printf("%d", dp[N - 1][V]);
    return 0;
}

代码随想录

遍历顺序
我们知道01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。

而完全背包的物品是可以添加多次的,所以要从小到大去遍历

在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的!

因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了。

遍历物品在外层循环,遍历背包容量在内层循环,
一维数组

#include<bits/stdc++.h>

using namespace std;

int main() {
    
    
    int N, V;
    scanf("%d%d", &N, &V);
    vector<int> v(N);
    vector<int> w(N);
    for(int i = 0; i < N; i++) {
    
    
        scanf("%d %d", &v[i], &w[i]);
    }
    
    vector<int> dp(V + 1, 0);
    
    for(int i = 0; i < N; i++)
        for(int j = v[i]; j <= V; j++) {
    
    
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        }
        
    printf("%d", dp[V]);
    return 0;
}

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

我的题解

在这里插入图片描述

class Solution {
    
    
public:
    int change(int amount, vector<int>& coins) {
    
    
        vector<int> dp(amount + 1, 0); //dp[j] 金额为j时候有多少种方式凑成

        dp[0] = 1; //初始化
        for(int i = 0; i < coins.size(); i++) {
    
    
            for(int j = coins[i]; j <= amount; j++) {
    
    
                dp[j] += dp[j - coins[i]];  //递推公式
            }
        }
        return dp[amount];
    }
};

代码随想录

虽然解法一样,但我明显没有想那么多。

遍历顺序get

先来看 外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)的情况。

for (int i = 0; i < coins.size(); i++) {
    
     // 遍历物品
    for (int j = coins[i]; j <= amount; j++) {
    
     // 遍历背包容量
        dp[j] += dp[j - coins[i]];
    }
}

假设:coins[0] = 1,coins[1] = 5。

那么就是先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况。

所以这种遍历顺序中dp[j]里计算的是组合数!
在这里插入图片描述
如果把两个for交换顺序,代码如下:

for (int j = 0; j <= amount; j++) {
    
     // 遍历背包容量
    for (int i = 0; i < coins.size(); i++) {
    
     // 遍历物品
        if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
    }
}

背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。

此时dp[j]里算出来的就是排列数!
在这里插入图片描述

377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。

题目数据保证答案符合 32 位整数范围。

示例 1:

输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

示例 2:

输入:nums = [9], target = 3
输出:0

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 1000
  • nums 中的所有元素 互不相同
  • 1 <= target <= 1000

进阶: 如果给定的数组中含有负数会发生什么?问题会产生何种变化?如果允许负数出现,需要向题目中添加哪些限制条件?

代码随想录

直接想起上一道题,先遍历物品再遍历背包是排列,先遍历背包再遍历物品是组合。
但是注意测试用例有两个数相加超过int的数据,所以需要在if里加上dp[i] < INT_MAX - dp[i - num]。

本题也可以用回溯暴搜,如果需要把排列都列出来,回溯可以解

class Solution {
    
    
public:
    int combinationSum4(vector<int>& nums, int target) {
    
    
        vector<int> dp(target + 1, 0);
        dp[0] = 1;
        for(int j = 0; j <= target; j++) {
    
    
            for(int i = 0; i < nums.size(); i++) {
    
    
                if(j >= nums[i] && dp[j] < INT_MAX - dp[j - nums[i]]) dp[j] += dp[j - nums[i]];
            }
        }
        return dp[target];
    }
};

猜你喜欢

转载自blog.csdn.net/weixin_51233575/article/details/129966908
今日推荐