代码随想录算法训练营Day-34动态规划03 | 01背包问题 二维、01背包问题 一维、416. 分割等和子集

目录

[01背包问题 二维](#01背包问题 二维)

动规四部曲:

[1. dp数组含义](#1. dp数组含义)

[2. 递推公式](#2. 递推公式)

[3. 初始化方式](#3. 初始化方式)

[4. 遍历顺序](#4. 遍历顺序)

[01背包问题 一维](#01背包问题 一维)

一维压缩可行性分析

动规四部曲:

[1. dp数组dp[j]含义](#1. dp数组dp[j]含义)

[2. 递推公式](#2. 递推公式)

[3. 初始化方式](#3. 初始化方式)

[4. 遍历顺序](#4. 遍历顺序)

[416. 分割等和子集](#416. 分割等和子集)

思路


背包问题

有一个重量数组记录第i个物品的重量;一个价值数组记录第i个物品的价值,物品数量为m;有一个背包容量n,问把m个物品任选放入容量为n的背包中,求最大价值

01背包问题 二维

动规四部曲:

1. dp数组含义

j容量的背包,从前i个物品任意选取放入,得到的最大价值

2. 递推公式

当前最大价值有两种情况:放第i个物品和不放第i个物品

不放第i个物品:dp[i-1][j];

放第i个物品:dp[i-1][j-weight[i]]+val[i]

解释:首先腾出放第i个物品的空间->+val[i],剩下的空间从前i-1个物品任取放入取最大价值->dp[i-1][j-weight[i]]

3. 初始化方式

首先定义dp数组时,初始化为0,覆盖了第一列;

然后处理第一行,仅第一个物品放入背包,如果容量够,值变成val[0]

这样做的目的是,因为递推是从左上到右下 ,所以要初始化第一行和第一列

4. 遍历顺序

先物品后背包/先背包后物品都可以

**先物品后背包:**遍历到第i个物品时,意味着从前i个物品中任选物品;然后从小到大遍历背包空间,依次看每种空间大小可以得到的最大价值

**先背包后物品:**遍历到空间大小为j时,然后遍历物品,遍历到第i个物品,就尝试把前i个物品任选放入这j个空间,看得到的最大价值

cpp 复制代码
#include<iostream>
using namespace std;
#include<vector>
int main(){
    int m,n;
    cin>>m>>n;
    vector<int> weight(m);
    vector<int> val(m);
    for(int i=0;i<m;i++){
        cin>>weight[i];
    }
    for(int j=0;j<m;j++){
        cin>>val[j];
    }

    //dp[i][j]含义:j容量的背包,从前i个物品任意选取放入,得到的最大价值
    vector<vector<int>> dp(m,vector<int>(n+1,0));
    
    //初始化dp数组:仅第一个物品放入背包,如果容量够,值变成val[0]
    for(int j=weight[0]; j<=n; j++) dp[0][j] = val[0];

    //递推公式:当前最大价值有两种情况:放第i个物品和不放第i个物品
    //不放第i个物品:dp[i-1][j]; 
    //放第i个物品:dp[i-1][j-weight[i]]+val[i]
    //解释:首先腾出放第i个物品的空间->+val[i],剩下的空间从前i-1个物品任取放入取最大价值->dp[i-1][j-weight[i]]

    //遍历顺序:先物品后背包/先背包后物品都可以
    //先物品后背包:遍历到第i个物品时,意味着从前i个物品中任选物品;然后从小到大遍历背包空间,依次看每种空间大小可以得到的最大价值
    //先背包后物品:遍历到空间大小为j时,然后遍历物品,遍历到第i个物品,就尝试把前i个物品任选放入这j个空间,看得到的最大价值

    for(int i=1; i<m; i++){
        for(int j=0; j<=n; j++){
            if(j < weight[i]) dp[i][j] =  dp[i-1][j];
            else dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+val[i]);
        }
    }

    //打印dp数组
    // for(int i=0; i<m; i++){
    //     for(int j=0; j<=n; j++){
    //         cout<<dp[i][j]<<" ";
    //     }
    //     cout<<endl;
    // }

    cout<<dp[m-1][n]<<endl;

    return 0;

}

01背包问题 一维

一维压缩可行性分析

把二维dp压缩为一维dp,可行的原因是二维的递推公式:

不放第i个物品:dp[i-1][j];

放第i个物品:dp[i-1][j-weight[i]]+val[i]

仅依赖上层数据,所以只需维护一行数据(一个一维数组),在该数组上迭代更新即可

动规四部曲:

1. dp数组**dp[j]**含义

j容量的背包,从所有物品中(i个,但没体现在dp上)得任选放入,得到的最大价值

2. 递推公式

当前最大价值有两种情况:放第i个物品和不放第i个物品

不放第i个物品:dp[j];

放第i个物品:dp[j-weight[i]]+val[i]

解释:首先腾出放第i个物品的空间->+val[i],剩下的空间从前i-1个物品任取放入取最大价值->dp[j-weight[i]]

3. 初始化方式

对于背包问题一维dp,递推依赖上一循环的dp和第i个物品的重量,所以只需要将一维dp初始化为0即可开始迭代

4. 遍历顺序

先物品后背包+背包倒序遍历+背包从n遍历到weight[i]

**为什么倒序遍历:**因为递推公式会调用dp[j-weight[i]],如果正序遍历,则无法保证dp[j-weight[i]]是在多少个物品里面选择然后放入的,所以有可能出现重复放入同一物品的情况

举例:假设遍历到物品0,dp[1]容量够,给dp[1]赋了值,然后后面的大容量肯定也能放下物品0,所以会调用dp[1]的值再加上val[0],导致重复选取。

**为什么必须先物品后背包:**如果先背包后物品,由于背包需倒序,所以一开始是最大容量n,然后开始下一层遍历物品,假设前两个物品可以一起放下,那么开始执行循环逻辑,第一个物品可以放下,dp[n]更新为val[0];第二个物品也可以放下,dp[n] = max(dp[j], dp[j-weight[i]]+val[i]),也就是更新为物品0和物品1之间的最大值,(因为dp[j-weight[i]]还没有遍历到,一定是0)。

这样就导致只放下了物品0或者物品1,无法把两个物品都放入

**为什么背包从n遍历到weight[i]:**因为如果背包容量小于weight[i],第i个物品无需考虑放不放,因为一定放不下,直接保持原状就好,也就是不迭代j<weight[i]的部分。

cpp 复制代码
#include<iostream>
using namespace std;
#include<vector>
int main(){
    int m,n;
    cin>>m>>n;
    vector<int> weight(m);
    vector<int> val(m);

    for(int i=0;i<m;i++){
        cin>>weight[i];
    }
    for(int j=0;j<m;j++){
        cin>>val[j];
    }

    //dp[j]含义:j容量的背包,所有物品任选放入,得到的最大价值
    vector<int> dp(n+1,0);
    
    //初始化dp数组:直接初始化为0,这样就满足更新条件

    for(int i=0; i<m; ++i){
        for(int j=n; j>=weight[i]; --j){
            dp[j] = max(dp[j], dp[j-weight[i]]+val[i]);
        }
    }

    //打印dp数组
    // for(int i=0; i<m; i++){
    //     for(int j=0; j<=n; j++){
    //         cout<<dp[i][j]<<" ";
    //     }
    //     cout<<endl;
    // }

    cout<<dp[n]<<endl;

    return 0;

}

416. 分割等和子集

思路

本题可抽象为一个背包问题:从数组nums中选取物品,放入容量为sum/2的背包中,看背包能否**装满,**装满就意味着从数组中选取元素可以填充为数组和的一半,代表可以分割成等和子集

还需注意,本题需要判断是否装满的逻辑,实现方式为:把物品的重量和价值设为相同值,即令nums中元素既为其重量也为其价值:

如果填满了,则总 重量=背包容量=target ,总价值=dp[target] = 总重量,推导出 dp[target] = target;

如果填不满,则target≠dp[target]

剩余思路和01背包 一维问题完全一样,最终返回dp[target] == target即可

cpp 复制代码
class Solution {
public:
    bool canPartition(vector<int>& nums) {

    int sum = 0;
    for(auto i :nums) sum+=i;
    if(sum%2 !=0) return false;
    int target = sum/2;
    //dp[j]含义:j容量的背包,所有物品任选放入,得到的最大价值
    vector<int> dp(target+1,0);
    
    //初始化dp数组:直接初始化为0,这样就满足更新条件

    for(int i=0; i<nums.size(); ++i){
        for(int j=target; j>=nums[i]; --j){
            dp[j] = max(dp[j], dp[j-nums[i]]+nums[i]);
        }
    }
    return dp[target] == target;
    }
};

相关推荐
华清远见IT开放实验室1 小时前
AI 算法核心知识清单(深度实战版2)
人工智能·深度学习·算法·机器学习·ai·模型训练
前进吧-程序员2 小时前
C++ 内存到底分配在哪?
java·jvm·c++
兩尛2 小时前
c++面试常问1
jvm·c++·面试
空中海2 小时前
Redis 从零到精通:9大数据结构 × 11个高频工程实战场景完全手册
数据结构·数据库·redis
点云侠2 小时前
隧道中线提取的优化方法
c++·算法·最小二乘法
汉克老师2 小时前
GESP2023年6月认证C++三级( 第二部分判断题(1-10))
c++·数组·位运算·进制·gesp三级·gesp3级
圣保罗的大教堂2 小时前
leetcode 3761. 镜像对之间最小绝对距离 中等
leetcode
minji...2 小时前
Linux 线程同步与互斥(五) 日志,线程池
linux·运维·服务器·开发语言·c++·算法
python_DONG2 小时前
响应面法(Response Surface Methodology, RSM)单目标优化算法
算法·数学建模