动态规划——完全背包问题

目录

一、完全背包

二、零钱兑换

三、零钱兑换II

四、完全平方数


一、完全背包

完全背包可以描述为:有n件物品和一个最多能背体积为 v 的背包。第 i 件物品的体积是 v[i],得到的价值是value[i] 。每件物品有无限多个,求解将哪些物品装入背包里物品价值总和最大。

在完全背包问题中,对于每件物品,有多种状态:不选,选一个,选两个......

题目

完全背包

题目解析

第一问的所有情况就是上图红色方框的内容, 第二问的所有情况就是上图蓝色方框的内容。

解决第一问

第一步:确定状态表示

dp[i][j]:表示从前 i 个物品中挑选,总体积不超过 j 的所有选法中,能挑选出来的物品价值最大值。

第二步:推出状态转移方程

前面我们已经说明了,完全背包中的每件物品的数量有无限个,所以对于每件物品来说,有选或者选1个,选2个...... 多种状态。根据最后一步的状况,分情况讨论,即要么不选择 i 号物品,要么选 1个 i 号物品,要么选 2个 i 号物品,要么选 3个 i 号物品......

选一个 i 物品,这时就有一个v[i]的体积了,然后仅需去 1 ~ i - 1 区间选不超过 j - v[i] 的最大价值,然后再加上 i 的价值。

选两个 i 物品,这时就有两个v[i]的体积了,然后仅需去 1 ~ i - 1 区间选不超过 j - 2*v[i] 的最大价值,然后再加上两个 i 的价值。

选n个 i 物品,这时就有n个v[i]的体积了,然后仅需去 1 ~ i - 1 区间选不超过 j - n*v[i] 的最大价值,然后再加上n个 i 的价值。

第三步:初始化dp表

第一列不需要初始化。

第一行表示没有物品,当 j 为0的时候,要凑成不超过 j,只要不选就行了,最大价值是0。如果j为1、2、3......但是没有物品可选,所以最大价值也是0。

解决第二问

第一步:确定状态表示

dp[i][j]:表示从前 i 个物品中挑选,总体积恰好为 j 的所有选法中,能挑选出来的物品价值最大值。

第二步:推出状态转移方程

状态转移方程的推导方法和第一问相同,只是有一些细节问题需要注意。

题目要求选出的物品中,总体积要恰好为 j ,那么就有可能怎么选也凑不出物品的总体积恰好为 j 的情况,那么我们就不能使用这种情况,可以用 dp[i][j] == -1 来表示凑不出物品的总体积恰好为 j 的情况,即这种情况不存在。

第三步:初始化dp表

第一列不需要初始化。

第一行表示没有物品,当 j 为0的时候,要凑成不超过 j,只要不选就行了,最大价值是0。如果j为1、2、3......因为没有物品可选,所以这些情况都不存在,值为-1。

解题代码:

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() 
{
    int n, V;
    cin >> n >> V;
    vector<int> v(n);
    vector<int> w(n);
    for(int i = 0; i < n; i++)
        cin >> v[i] >> w[i];

    vector<vector<int>> dp1(n+1, vector<int>(V+1));
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= V; j++)
        {
            dp1[i][j] = dp1[i-1][j];
            if(j >= v[i-1])
            {
                int ret = max(dp1[i-1][j], dp1[i][j-v[i-1]] + w[i-1]);
                dp1[i][j] = max(dp1[i][j], ret);
            }
        }
    }

    vector<vector<int>> dp2(n+1, vector<int>(V+1));
    for(int i = 1; i <= V; i++)
        dp2[0][i] = -1; // 用-1来表示该情况不存在
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= V; j++)
        {
            dp2[i][j] = dp2[i-1][j];
            if(j >= v[i-1] && dp2[i][j-v[i-1]] != -1)
            {
                int ret = max(dp2[i-1][j], dp2[i][j-v[i-1]] + w[i-1]);
                dp2[i][j] = max(dp2[i][j], ret);
            }
        }
    }

    cout << dp1[n][V] << endl;
    cout << (dp2[n][V] == -1 ? 0 : dp2[n][V]) << endl;
    return 0;
}

二、零钱兑换

零钱兑换

第一步:确定状态表示

dp[i][j]:表示从前 i 个硬币中挑选,使其总和恰好等于j,所有选法中,最少的硬币个数。

第二步:推出状态转移方程

第三步:初始化dp表

第一列不用初始化。第一行表示没有硬币,当 j 为0表示硬币总和为0,不选就行了。

解题代码:

cpp 复制代码
class Solution 
{
public:
    int coinChange(vector<int>& coins, int amount) 
    {
        int m = coins.size();
        vector<vector<int>> dp(m+1, vector<int>(amount+1));
        for(int i = 1; i <= amount; i++)
            dp[0][i] = 0x3f3f3f3f;
        for(int i = 1; i <= m; i++)
        {
            for(int j = 0; j <= amount; j++)
            {
                dp[i][j] = dp[i-1][j];
                if(j >= coins[i-1])
                    dp[i][j] = min(dp[i][j], dp[i][j-coins[i-1]]+1);
            }
        }
        return dp[m][amount] == 0x3f3f3f3f ? -1 : dp[m][amount];
    }
};

三、零钱兑换II

零钱兑换II

第一步:确定状态表示

dp[i][j]:表示从前 i 个硬币中挑选,使其总和恰好等于j,一共有多少种选法。

第二步:推出状态转移方程

第三步:初始化dp表

第一列不用初始化。

第一行表示没有硬币,当 j = 0时表示总和为0,不选就行了,这是一种选法。当j = 1、2、3...,因为没有硬币,所以根本凑不出总和等于j的选法。直接都给0就行了。

解题代码:

cpp 复制代码
class Solution 
{
public:
    int change(int amount, vector<int>& coins) 
    {
        int m = coins.size();
        vector<vector<double>> dp(m+1, vector<double>(amount+1));
        dp[0][0] = 1;
        for(int i = 1; i <= m; i++)
        {
            for(int j = 0; j <= amount; j++)
            {
                dp[i][j] += dp[i-1][j];
                if(j >= coins[i-1])
                    dp[i][j] += dp[i][j-coins[i-1]];
            }
        }
        return dp[m][amount];
    }
};

四、完全平方数

完全平方数

第一步:确定状态表示

dp[i][j]:表示从前 i 个完全平方数种挑选,使其总和恰好等于 j,所有选法中,平方数的最少数量。

第二步:推出状态转移方程

第三步:初始化dp表

第一列不用初始化。

第一行表示完全平方数为0,当 j = 0表示总和为0,不选就行了,最少数量为0。当 j = 1、2...时完全平方数为0,根本凑不出和为 j,然后我们填dp[i][j]要最小值,为了不让这些位置得值影响填表,因此可以给0x3f3f3f3f。

解题代码:

cpp 复制代码
class Solution 
{
public:
    int numSquares(int n) 
    {
        int m = sqrt(n);
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        for(int i = 1; i <= n; i++)
            dp[0][i] = 0x3f3f3f3f;
        for(int i = 1; i <= m; i++)
        {
            for(int j = 0; j <= n; j++)
            {
                dp[i][j] = dp[i-1][j];
                if(j >= i*i)
                    dp[i][j] = min(dp[i][j], dp[i][j-i*i]+1);
            }
        }
        return dp[m][n];
    }
};
相关推荐
九圣残炎4 分钟前
【从零开始的LeetCode-算法】3239. 最少翻转次数使二进制矩阵回文 I
java·算法·leetcode
环境感知7 分钟前
基于MATLAB的激光雷达与相机联合标定原理及实现方法——以标定板为例
开发语言·人工智能·数码相机·算法·matlab
Romanticroom37 分钟前
图论之最小生成树计数(最小生成树的应用)
算法·图论
TT编程2 小时前
力扣(leetcode)题目总结——动态规划篇
c++·leetcode·面试·动态规划
SuhyOvO2 小时前
std::sort的底层原理(混合排序算法)
c++·算法·stl·排序算法
licy__2 小时前
python常用的排序算法
python·算法·排序算法
行码棋2 小时前
【机器学习】决策树算法原理详解
算法·决策树·机器学习
卷卷的小趴菜学编程2 小时前
类和对象(中)
java·c语言·开发语言·数据结构·c++·算法·链表
不打灰的小刘2 小时前
基于自动反馈的大语言模型纠错策略综述
人工智能·算法·语言模型·chatgpt