动态规划笔记 - 上

本文详细介绍了动态规划在背包问题中的应用,包括01背包、完全背包和各种优化技巧,如滚动数组和空间优化。讨论了斐波那契数、爬楼梯问题的扩展以及整数拆分等典型案例。
摘要由CSDN通过智能技术生成

ps:动态规划章节 - 背包问题

动态规划!

image-20240310125139854



很重要的五部曲!

image-20240310125236505



斐波那契数

这道题主要是用来加深理解dp做题步骤的

image-20240319153033712



爬楼梯

image-20240319155709397



使用最小花费爬楼梯

image-20240319173410493



和前面几题一样,可以优化空间复杂度

image-20240319173353234



不同路径

当前状态和前面的状态有关直接dp

一开始想的是BFS不过太久没写了 就没试,DFS会超时

image-20240319175248704



不同路径II

就是多了有障碍物的情况

image-20240326145944426



整数拆分

是一道不太好想的题

关键是要明确我们定义的dp是啥含义

n较大的数可以拆成 较小n * 某个数 得来的,所以我们可以使用dp

思路: 拆数字,要至少拆成两份,然后里面还可以继续拆

image-20240319200939002



优化后的

image-20240319200311205





01背包理论基础

只需掌握01背包和完全背包就可以了

416.分割等和子集1



01背包暴力解法

image-20240326161810558



二维数组解法

对于背包问题,有一种写法, 是使用二维数组,即dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

image-20240326161945699



递推公式

image-20240326162016178



初始化

image-20240326162302123



遍历顺序

有两个遍历的维度:物品与背包重量

那么问题来了,先遍历 物品还是先遍历背包重量呢?

其实都可以!! 但是先遍历物品更好理解





01背包典例



递推公式还是很巧的

#include<bits/stdc++.h>

using namespace std;

// dp[i][j] 表示从下标0到i的物品里面任意取,放进容量为j的背包里,最大的val为dp[i][j]
int val[5005], spa[5005], dp[5005][5005];
int main()
{
    int m,n;
    cin>>m>>n;
    for(int i=0;i<m;i++) cin>>spa[i];
    for(int i=0;i<m;i++) cin>>val[i];

    //初始化
    for(int i=spa[0];i<=n;i++) dp[0][i] = val[0];

    for(int i=1; i<m; i++){ //从没有初始化的地方遍历材料
        for(int j=0 ; j<=n ;j++){ //遍历容量
            if(j < spa[i]) dp[i][j] = dp[i-1][j];  //不能装下时
            else dp[i][j] = max(dp[i-1][j], dp[i-1][j-spa[i]]+val[i]);  //可以装下时,有两种选择
        }
    }

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


    return 0;
}





01背包理论基础(滚动数组)

[外链图片转存中...(img-D6xuPoBg-1713150382138)]



步骤

  • 关键是遍历容量的时候是倒序
  • 还有遍历的边界条件

image-20240327110858914



为什么要倒序?

简单理解就是正序遍历会重复统计(状态重复),倒序遍历不会

image-20240327111528640



01背包典例

image-20240327112636751

#include<bits/stdc++.h>

using namespace std;

int dp[5005], spa[5005], val[5005];  //dp[j] 表示容量为j的背包里最大能装入的价值为dp[j]
int main()
{
    int m,n;
    cin>>m>>n;
    for(int i=0;i<m;i++) cin>>spa[i];
    for(int i=0;i<m;i++) cin>>val[i];

    //默认初始化为0
    
    for(int i=0;i<m;i++){  //遍历物品
        for(int j=n;j>=spa[i];j--){  //倒序遍历 容量
            dp[j] = max(dp[j], dp[j-spa[i]]+val[i]);  //max(不放i, 放i)
        }
    }
    cout<<dp[m];
    
    
    return 0;
}



分割等和子集

如何把它抽象成01背包问题的,我觉得还是挺巧的

image-20240327160342377



再次搞清楚什么是01背包

有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大

关键点是总和最大

为什么本题可以用01背包做

  • 背包的体积为sum / 2
  • 背包要放入的商品**(集合里的元素)重量为 元素的数值,价值也为元素的数值**
  • 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
  • 背包中每一个元素是不可重复放入。

我们要求目标值,我们就把它抽象成一个背包,如果这个目标值背包可以被装满,说明我们可以取得这个目标值





最后一块石头的重量II

很难想象它是一个01背包,我是看了老师的提示(分成两堆)才做出来的

本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了

[外链图片转存中…(img-F4GLwhWh-1713150382140)]





小总结

二维01背包

虽然老师后面用的都是一维的,但是二维的其实也挺简单好记的

image-20240327163848406



一维01背包

image-20240327164003798

后面的两道题都是等分子集的问题, 遇到需要等分子集的时候可以想想能不能01背包吧

image-20240327172303180



不对,上面的那两道题其本质还是求背包最多能装多少



目标和

这道题是可以用DFS的,下次复习的时候,拿DFS做一遍

本题则是装满有几种方法。其实这就是一个组合问题了。



数学问题

分成两堆,正好两个方程,解方程

image-20240328110727500



装满背包有几种方法 – 递推公式

image-20240328111217318



目前遇到两种题型

  1. 背包最多能装多少val – 所以有个关键词是最多 ,双指针有个关键词是最少

  2. 也有题型是求装满的时候,最少装多少val. 所以最多最少也都可以考虑dp . 双指针有个关键词是最少

  3. 装满背包有几种方法

感觉dp的题还是得多做,把一些基础的题型都过了





一和零

虽然第一次看见这种题感觉不好想,但不可否认它是一道好题

两个维度(0 和 1,然后抽象成两种重量) , 从一维延伸成二维真的很妙

最多有多少个子集,转换为背包里面最多可以放几个子集

image-20240328200959321


在对比一下一维的

image-20240328201153536





完全背包理论基础

有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件

01背包和完全背包唯一不同就是体现在遍历顺序

完全背包例题

image-20240330171321184



为什么要正序遍历

image-20240330171408153





image-20240330171604171





零钱兑换II

是一道很经典的题,也引出了一个概念,组合数&排序数

[外链图片转存中…(img-9IquzZkV-1713150382143)]

image-20240330172456475



小结论

image-20240330172933882


b站一些优质评论– 便于理解

image-20240330213127096



小总结

求有几种方法时

[外链图片转存中...(img-HHo339bf-1713150382144)]



二维01背包

image-20240330213538928


完全背包

image-20240330213651648



组合总和IV

注意一下先遍历背包的一些细节

  • j 的初始化

  • 有 if 条件

    先遍历容量的时候,j从0或者从1开始都可以

[外链图片转存中...(img-UxVZTNBg-1713150382145)]





爬楼梯(加强版)

求排列数的完全背包

[外链图片转存中...(img-HQKWKGFs-1713150382145)]





零钱兑换

一开始没a出来,后面看题解后发现是初始化那块不太理解,有问题。

动规五部曲,做不出的可以打印dp数组看看,自己排排错。

最小的硬币个数 = 最小的背包val

最小的背包val关键点

  • 初始化
  • 递推公式

image-20240331110445400



image-20240331111112520





完全平方数

直接秒了哈哈哈

image-20240331112909470





单词拆分

很少做这种题,还是挺不好想的

难点:



  • 这里是完全背包的 排列数 问题
  • dp[] 数组的定义不好想
  • for循环那里 ,i 是长度,j 是下标。 求对应关系的时候得动手模拟



image-20240331203812048









背包问题大总结

image-20240403100832655
image-20240403100847398

image-20240403100940599

img





此文章用于笔者记录学习,也希望对你有帮助

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值