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

目录

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

动规四部曲:

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

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

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

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

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

一维压缩可行性分析

动规四部曲:

[1. dp数组dpj含义](#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个物品:dpi-1j;

放第i个物品:dpi-1j-weight\[i]+vali

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

3. 初始化方式

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

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

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

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个物品:dpi-1j;

放第i个物品:dpi-1j-weight\[i]+vali

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

动规四部曲:

1. dp数组**dpj**含义

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

2. 递推公式

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

不放第i个物品:dpj;

放第i个物品:dpj-weight\[i]+vali

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

3. 初始化方式

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

4. 遍历顺序

先物品后背包+背包倒序遍历+背包从n遍历到weighti

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

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

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

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

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

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 ,总价值=dptarget = 总重量,推导出 dptarget = target;

如果填不满,则target≠dptarget

剩余思路和01背包 一维问题完全一样,最终返回dptarget == 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;
    }
};

相关推荐
写代码写到手抽筋4 小时前
5G上行DCI字段判定:端口 流数 PMI选择详解
java·算法·5g
xieliyu.4 小时前
Java算法精讲:双指针(二)
java·开发语言·算法
苏宸啊4 小时前
IPC管道
linux·c++
BestOrNothing_20155 小时前
ROS2 话题通信实战:消息对象、Publisher 发布器与 Subscriber 订阅器保姆级教程
c++·ros2·subscriber·publisher·话题通信
wayz115 小时前
Momentum:PSL(心理线指标)技术指标详解
算法·金融·数据分析·量化交易·特征工程
8Qi85 小时前
LeetCode 213:打家劫舍 II(House Robber II)—— 题解 ✅
算法·leetcode·职场和发展·动态规划
三品吉他手会点灯6 小时前
C语言学习笔记 - 44.运算符和表达式 - 运算符2 - 除法与取余运算符
c语言·开发语言·笔记·算法
乐迪信息6 小时前
乐迪信息:AI算法盒子实时识别船舶烟雾与火焰异常
大数据·人工智能·算法·安全·目标跟踪
J-Tony116 小时前
【JVM】根可达算法
jvm·算法
艾iYYY6 小时前
string 类的模拟实现
android·服务器·c语言·c++·算法