代码随想录算法训练营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;
    }
};

相关推荐
码匠许师傅7 小时前
【C++ 面试真题】C++ 的 const 和 constexpr 有什么区别?
c++·面试
坚持编程的菜鸟8 小时前
模拟实现memmove
c语言·算法·模拟实现memmove
wabs6669 小时前
关于图论【最短路径之Dijkstra算法(堆优化版)|卡码网47.参加科学大会的思考】
数据结构·算法·图论·优先级队列·邻接表·小顶堆·卡码网
果果燕10 小时前
实习笔记(一):NFS、Samba、VNC、Git、CMake、systemd、内核、交叉编译
linux·arm开发·c++·笔记·git
坚持编程的菜鸟10 小时前
编写判断大小端程序
c语言·算法·判断大小端
papaofdoudou10 小时前
判断排列逆序奇偶性的乘积判别法(范德蒙德符号法)
人工智能·算法
牛艺翔10 小时前
C++基础
开发语言·c++
键盘会跳舞10 小时前
C++ :容器适配器stack源码级拆解
开发语言·c++··stack·先进后出
linux-hzh10 小时前
百日算法修炼 · Day 03
java·算法
问商十三载11 小时前
RAG 检索效果差怎么排查?2026 五层诊断法完整指南
开发语言·人工智能·windows·python·算法