力扣日记4.16~4.17-【动态规划篇】01背包问题

力扣日记:【动态规划篇】01背包问题

日期:2024.4.16-4.17
参考:代码随想录、卡码网

携带研究材料

题目描述

难度:

题目描述

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。
小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。

输入描述

第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。
第二行包含 M 个正整数,代表每种研究材料的所占空间。
第三行包含 M 个正整数,代表每种研究材料的价值。

输出描述

输出一个整数,代表小明能够携带的研究材料的最大价值。

输入示例

6 1
2 2 3 1 5 2
2 3 1 5 4 3

输出示例

5

提示信息

小明能够携带 6 种研究材料,但是行李空间只有 1,而占用空间为 1 的研究材料价值为 5,所以最终答案输出 5。

数据范围:

1 <= N <= 5000
1 <= M <= 5000
研究材料占用空间和价值都小于等于 1000

题解

1. 用二维dp数组解决


/*
    01 背包问题:
    
    定义 dp数组:dp[i][j] 表示当背包容量为j时,从下标为 0-i 的物品中任取,所能达到的最大价值
    推导 递推关系
    
    关键是理解从 i-1 到 i 的递推关系
    dp[i - 1] -> dp[i] 有两种情况:即 不取物品i 和 不取物品i
    对于不取物品i(即背包没有容量放置i),则 此时背包的价值为  dp[i-1][j]  即从0-i-1任取和从0-i任取是一样的(i都无法取)
    对于取物品i,则此时背包的价值有两部分组成,即 一部分用于装物品i,一部分用于装物品0-(i-1)(并使其价值最大)
    前者的容量为 weight[i],对应价值为value[i],后者的容量为 j-weight[i],对应价值为dp[i-1][j-weight[i]]
    则dp[i][j]为两种情况的最大值
    dp[i][j] = max{dp[i-1][j], dp[i-1][j-weight[i]] + value[i]}
*/

//二维dp数组实现
#include <bits/stdc++.h>
using namespace std;

int bagMaxValue(vector<int> weights, vector<int> values, int bagSize) {
    int num = weights.size();   // 物品数量
    // 定义dp数组(二维)
    vector<vector<int>> dp(num, vector<int>(bagSize + 1, 0));
    // 初始化(第一行和第一列)
    // 对于dp[i][0],由于背包容量为0,则价值一定为0(包含在初始化中)
    // 对于dp[0][j],则只有当j>=weight[0],背包价值才为value[0]
    for (int j = weights[0]; j <= bagSize; j++) {
        dp[0][j] = values[0];
    }
    // 遍历(遍历顺序:先遍历物品再遍历背包 或反之皆可)
    // 由于 dp[i][j] 由左上部分推导得到,所有从左到右、从上往下遍历
    // 先遍历背包,再遍历物品更好理解(?),理解过程与上面的递推推导是一样的
    
    for (int j = 1; j <= bagSize; j++) {
        // 当背包为j时,从 i-1 到 i 的递推
        for (int i = 1; i < num; i++) {
        	// 如果装不下这个物品,那么就继承dp[i - 1][j]的值
            if (j < weights[i]) dp[i][j] = dp[i-1][j];  // 因为下面的索引要j-weights[i]
            // 如果能装下,就将值更新为 不装这个物品的最大值 和 装这个物品的最大值 中的 最大值
            // 装这个物品的最大值由容量为j - weight[i]的包任意放入序号为[0, i - 1]的最大值 + 该物品的价值构成
            else {
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-weights[i]] + values[i]);
            }
        }
    }
    
    return dp[num - 1][bagSize];    // 当背包容量为bagSize,从0-num的物品中任取能得到的最大价值
    
}

int n, bagweight;// bagweight代表行李箱空间
void solve(int bagweight) {
    vector<int> weight(n, 0); // 存储每件物品所占空间
    vector<int> value(n, 0);  // 存储每件物品价值
    for(int i = 0; i < n; ++i) {
        cin >> weight[i];
    }
    for(int j = 0; j < n; ++j) {
        cin >> value[j];
    }
    int result = bagMaxValue(weight, value, bagweight);
    cout << result << endl;
}

int main() {
    while(cin >> n >> bagweight) {
        solve(bagweight);
    }
    return 0;
}
2. 用一维dp数组解决
// 一维dp数组实现
#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 读取 M 和 N
    int M, N;
    cin >> M >> N;

    vector<int> costs(M);
    vector<int> values(M);

    for (int i = 0; i < M; i++) {
        cin >> costs[i];
    }
    for (int j = 0; j < M; j++) {
        cin >> values[j];
    }

    // 创建一个动态规划数组dp,初始值为0
    // dp[j] 表示 容量j的背包能装的最大价值
    vector<int> dp(N + 1, 0);
    
    // 先遍历物品再遍历背包(背包从大到小遍历)
    for (int i = 0; i < M; i++) { // 遍历物品
        for (int j = N; j >= costs[i]; j--) {   // 再遍历背包(从大到小,要保证j>=weight[i])
        	// 考虑当前研究材料选择和不选择的情况,选择最大值
            dp[j] = max(dp[j], dp[j-costs[i]] + values[i]); // 注意dp[j]会根据物品i迭代M次
        }
    }
    /*
    i = 0   j = N,      dp[N] = max(dp[N], dp[N-costs[0]] + values[0]) = values[0]
            j = N-1,    dp[N-1] = max(dp[N-1], dp[N-1-costs[0]] + values[0]) = values[0]
            ...
            j = costs[0], dp[costs[0]] = max(dp[...], dp[0] + values[0]) = values[0]
    i = 1   j = N,      dp[N] = max(dp[N], dp[N-costs[1]] + values[1])
                    即看上一个迭代(i=0时)(不取物品1)的dp[N] 大 还是
                        取物品1后的价值(背包除去物品1后剩余容量的价值dp[N-costs[1]] 与物品1的价值 values[1] 之和)大
            j = N-1,    同理
    ……
    */
    
    // 输出dp[N],即在给定 N 行李空间可以携带的研究材料最大价值
    cout << dp[N] << endl;

    return 0;
}

复杂度

设M为背包容量,N为物品数量
二维dp数组解决:

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

一维dp数组解决:

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

思路总结

  • 二维数组解决
    • 更容易理解
    • 遍历顺序可以先遍历物品再遍历背包或反之,且两者均为正序遍历(从小到大遍历)
  • 一维数组解决
    • 需要在二维数组的基础上,理解 利用滚动数组降维 的思想(把上一层的值拷贝的下一层,使得dp[i][j]中的i的维度消去,变为dp[j])—— 还是不太理解。。。
    • 同时一维数组在遍历顺序上更有考究,需注意:
      • 对背包容量进行遍历时一定要从大到小进行遍历即倒序遍历(防止同一个物品多次放入背包)
      • 必须是先遍历物品再遍历背包(因为一维dp背包容量一定是要倒序遍历,如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。)
      • (可以通过举例推导模拟一下 )
  • 代码随想录思路:
  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值