【二十】【动态规划】879. 盈利计划、377. 组合总和 Ⅳ、96. 不同的二叉搜索树 ,三道题目深度解析

news2024/9/29 15:26:25

动态规划

动态规划就像是解决问题的一种策略,它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题,并将每个小问题的解保存起来。这样,当我们需要解决原始问题的时候,我们就可以直接利用已经计算好的小问题的解,而不需要重复计算。

动态规划与数学归纳法思想上十分相似。

数学归纳法:

  1. 基础步骤(base case):首先证明命题在最小的基础情况下成立。通常这是一个较简单的情况,可以直接验证命题是否成立。

  2. 归纳步骤(inductive step):假设命题在某个情况下成立,然后证明在下一个情况下也成立。这个证明可以通过推理推断出结论或使用一些已知的规律来得到。

通过反复迭代归纳步骤,我们可以推导出命题在所有情况下成立的结论。

动态规划:

  1. 状态表示:

  2. 状态转移方程:

  3. 初始化:

  4. 填表顺序:

  5. 返回值:

数学归纳法的基础步骤相当于动态规划中初始化步骤。

数学归纳法的归纳步骤相当于动态规划中推导状态转移方程。

动态规划的思想和数学归纳法思想类似。

在动态规划中,首先得到状态在最小的基础情况下的值,然后通过状态转移方程,得到下一个状态的值,反复迭代,最终得到我们期望的状态下的值。

接下来我们通过三道例题,深入理解动态规划思想,以及实现动态规划的具体步骤。

879. 盈利计划 - 力扣(LeetCode)

题目解析

状态表示

这个问题本质上是,

  1. 挑选出一些工作作为一个集合,这个集合满足某些要求,解决某些问题。而背包问题就是挑选出一些物品作为一个集合,这个集合满足某些要求,解决某些问题。

  2. 挑选出来的工作不可以无限选取,所以属于二维01背包问题。

背包问题的状态表示是,

定义dp[i][j]表示从前i个物品中挑选,总体积不超过j,所有选法中,所能达到的最大价值。

定义dp[i][j]表示从前i个物品中挑选,总体积恰好为j,所有选法中,所能达到的最大价值。

我们根据背包问题的状态表示,定义出该问题的状态表示。

因此我们可以定义,dp[i][j][k]表示,从前i个工作中挑选,总需人数不超过j,总利润不少于k,总盈利计划个数。

状态转移方程

状态转移方程通常都是根据最后一个位置的具体状况进行分类讨论。

  1. 如果不选择第i个工作, 此时只能从前i-1个工作中挑选出集合,此时dp[i][j][k]=dp[i-1][j][k]。

  2. 如果选择第i个工作, 此时第i个工作需要的人数为group[i-1],产生的利润为profit[i-1],本来总人数不能超过j,选取第i个工作后,总人数不能超过j-group[i-1],本来总利润需要不少于k,选取第i个工作后,总利润需要不少于k-profit[i-1],因此dp[i][j][k]=dp[i-1][j-group[i-1]][k-profit[i-1]]。表示在前i-1个工作中挑选集合的集合个数,每一个集合都加入第i个工作,此时的集合个数为dp[i][j][k]。此时还需要判断j-guoup[i-1],k-profit[i-1]是否小于0,的情况。小于0就越界了。

    1. 如果j-group[i-1]<0, 表示第i个工作需要的人数就大于了j,此时dp[i][j][k]=0。

    2. 如果j-group[i-1]>=0, 说明此时可以完成第i个工作,dp[i][j][k]=dp[i-1][j-group[i-1][k-profit[i-1]],还需要考虑k-profit[i-1]的正负性。

      1. 如果k-profit[i-1]<0, 表示第i个工作产生的利润就可以满足最低需求利润,所以再从前面工作中挑选,不需要考虑它的利润条件,即产生的利润大于等于0即可。此时dp[i][j][k]=dp[i-1][j-group[i-1]][0]。

      2. 如果k-profit[i-1]>=0, 此时dp[i][j][k]=dp[i-1][j-group[i-1]][k-profit[i-1]]。

综上所述,将上述情况进行合并和简化,得到状态转移方程为,

 
        dp[i][j][k] = dp[i - 1][j][k];
        if (j - group[i - 1] >= 0)
            dp[i][j][k] = (dp[i][j][k]+dp[i - 1][j - group[i - 1]]
                             [max(0, k - profit[i - 1])])%MOD;

MOD=1e9+7,因为题目说得到的数可能很大,需要对MOD取余,所以两个数每相加,就对MOD取余。

初始化

根据状态转移方程,我们知道,推导(i,j,k)位置的状态需要用到(i-1,j,k)位置的状态,if判断保证j-group[i-1]一定不会小于0,dp[i - 1][j - group[i - 1]][max(0, k - profit[i - 1])],所以这个状态中只需要考虑(i-1)。所以我们需要初始化第一行,推导第一行的时候会发生越界的情况,此时没有前驱状态。

i==0,表示不选工作,人数不超过j,集合利润不少于k,集合个数,此时只有dp[0][j][0]=1,其他都为0。

故初始化为,

 
        for (int j = 0; j <= n; j++) {
            dp[0][j][0] = 1;
        }

填表顺序

根据状态转移方程,我们在推导(i,j,k)位置的状态时,需要用到(i-1,j,k)(i-1,j-group[i-1],k-profit[i-1])位置的状态。这些状态不会越界,初始化保证了他们不会越界。

  1. 固定i, i需要从小到大变化,当推导(i,j,k)位置状态时,(i-1,,)位置状态已经得到,所以j,k的变化可以从小到大,也可以从大到小。

  2. 固定j, j的变化需要从小到大,又因为需要用到(i-1,j,k)位置的状态,所以i的变化需要从小到大,此时k的变化可以从小到大也可以从大到小。

  3. 固定k, k的变化需要从小到大,又因为需要用到(i-1, j,k)位置的状态,所以i的变化需要从小到大,此时j的变化可以从小到大,也可以从大到小。

返回值

状态表示为dp[i][j][k]表示,从前i个工作中挑选,总需人数不超过j,总利润不少于k,总盈利计划个数。

结合题目意思,我们需要在前m个工作中挑选,总需人数不超过n,总利润不少于minProfit,总盈利计划个数。

返回dp[m][n][minProfit]

(m表示工作个数)

代码实现

 
class Solution {
public:
    int profitableSchemes(int n, int minProfit, vector<int>& group,
                          vector<int>& profit) {
        int m = group.size();
        int MOD = 1e9 + 7;
        vector<vector<vector<int>>> dp(
            m + 1, vector<vector<int>>(n + 1, vector<int>(minProfit + 1)));

        for (int j = 0; j <= n; j++) {
            dp[0][j][0] = 1;
        }
        for (int i = 1; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                for (int k = 0; k <= minProfit; k++) {
                    dp[i][j][k] = dp[i - 1][j][k];
                    if (j - group[i - 1] >= 0)
                        dp[i][j][k] = (dp[i][j][k]+dp[i - 1][j - group[i - 1]]
                                         [max(0, k - profit[i - 1])])%MOD;
                }
            }
        }
        return dp[m][n][minProfit];
    }
};

377. 组合总和 Ⅳ - 力扣(LeetCode)

题目解析

因此我们应该换一种思路,尝试正向解决这道问题。

在正向推导过程我们发现了重复子问题,求元素和为target一共有多少种排列方法数,等价于求元素和为target-第一个位置上的元素值,一共有多少种排列方法数,然后再考虑第一个位置上所有情况即可。

因此我们可以定义状态表示为dp[i]元素和为i的所有排列方法数。

状态表示

定义dp[i]元素和为i的所有排列方法数。

状态转移方程

  1. 如果有排列,

    1. 第一个位置为nums[0], 此时dp[i]=dp[i-nums[0]]。

    2. 第一个位置为nums[1], 此时dp[i]=dp[i-nums[1]]。

    3. 第一个位置为nums[2], 此时dp[i]=dp[i-nums[2]]。

    4. .......

  2. 如果没排列, dp[0]=1。

综上所述,状态转移方程为,dp[i]=dp[i-nums[0]]+dp[i-nums[1]]+..........

如果i-nums[0]<0,此时不存在。同时dp[0]=1。

 
            for(auto j:nums){
                if(j<=i){
                    dp[i]+=dp[i-j];
                }
            }

初始化

根据状态转移方程,dp[i]+=,所以每个位置都需要初始化为0。而没有排列的时候,dp[0]=1。

填表顺序

根据状态转移方程,推导i位置状态需要用到i-j位置的状态,所以i的变化需要从小到大。

返回值

状态表示为,定义dp[i]元素和为i的所有排列方法数。

结合题目意思,我们需要返回dp[target]

代码实现

 
class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<double>dp(target+1);
        dp[0]=1;
        for(int i=1;i<=target;i++){
            for(auto j:nums){
                if(j<=i){
                    dp[i]+=dp[i-j];
                }
            }
        }
        return dp[target];
    }
};

96. 不同的二叉搜索树 - 力扣(LeetCode)

题目解析

状态表示

定义dp[i]表示节点数为i,组成的二叉搜索树种类数。

状态转移方程

  1. 当节点数至少为3时,假设j作为根节点。 此时左边是1~(j-1),一共有(j-1)-1+1=j-1 个数, 右边是(j+1)~i,一共有i-(j+1)+1=i-j 个数,

      状态转移方程为,

     
    for(int j=1;j<=i;j++){
            dp[i]+=dp[j-1]*dp[i-j];
        }
  2. 当节点数为2时, dp[2]=2。

  3. 当节点数为1时, dp[1]=1。

  4. 当节点数为0时, dp[0]=1。此时表示没有节点,二叉搜索树的种类数,空也算是一种。表示左孩子为0时,种类数为右孩子种类数乘以1,或者右孩子为0时,种类数为左孩子种类数乘以1。

初始化

根据状态转移方程,dp[i]+=,所以每个状态先初始化为0。

填表顺序

根据状态转移方程,推导i位置状态时需要用到j-1,和i-j位置的状态,所以i的变化需要从小到大。

返回值

状态表示为,定义dp[i]表示节点数为i,组成的二叉搜索树种类数。

结合题目意思,我们需要返回dp[n]

代码实现

 
class Solution {
public:
    int numTrees(int n) {
        vector<int>dp(n+1);
        if(n==0||n==1) return 1;
        else if(n==2) return 2;
        dp[0]=1;
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=n;i++){
            for(int j=1;j<=i;j++){
                dp[i]+=dp[j-1]*dp[i-j];
            }
        }
        return dp[n];
        
    }
};

结尾

今天我们学习了动态规划的思想,动态规划思想和数学归纳法思想有一些类似,动态规划在模拟数学归纳法的过程,已知一个最简单的基础解,通过得到前项与后项的推导关系,由这个最简单的基础解,我们可以一步一步推导出我们希望得到的那个解,把我们得到的解依次存放在dp数组中,dp数组中对应的状态,就像是数列里面的每一项。最后感谢您阅读我的文章,对于动态规划系列,我会一直更新,如果您觉得内容有帮助,可以点赞加关注,以快速阅读最新文章。

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1379072.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Gauss消去法(C++)

文章目录 算法描述顺序Gauss消去法列选主元Gauss消去法全选主元Gauss消去法Gauss-Jordan消去法 算法实现顺序Gauss消去法列选主元Gauss消去法全选主元Gauss消去法列选主元Gauss-Jordan消去法 实例分析 Gauss消去法是求解线性方程组较为有效的方法, 它主要包括两个操作, 即消元和…

TypeScript学习笔记、鸿蒙开发学习笔记

变量定义方式 # 变量声明 let msg: string douzi console.log(msg) let num: number 20 console.log(num) let end: boolean true console.log("end" end) let a: any 10 a douzi console.log(a) let p {name:"douzi",age:20} console.log(p.name)…

30天精通Nodejs--第十七天:express-路由配置

目录 引言基础路由配置路由参数与查询参数路由前缀与子路由路由重定向结语 引言 上篇文章我们简单介绍了express的基础用法&#xff0c;包括express的安装、创建路由及项目启动&#xff0c;对express有了一个基础的了解&#xff0c;这篇开始我们将详细介绍express的一些高级用…

docker安装nacos+mysql+配置网络

一、配置网络 为什么要配置网络&#xff1f;因为 Nacos 内要连接MySQL数据库的&#xff0c;我的 MySQL 数据库也是用 Docker启动的&#xff0c;所以2个容器间要通信是需要配置他们使用相同的网络。这个操作要在启动Nacos容器之前。 注意&#xff1a;这里配置的网络只在镜像内部…

聚对苯二甲酸乙二醇酯PET的特性有哪些?UV胶水能够粘接聚对苯二甲酸乙二醇酯PET吗?又有哪些优势呢?

聚对苯二甲酸乙二醇酯&#xff08;Polyethylene Terephthalate&#xff0c;PET&#xff09;是一种常见的塑料材料&#xff0c;具有许多特性&#xff0c;包括&#xff1a; 1.化学式&#xff1a; PET的化学式为 (C10H8O4)n&#xff0c;其中n表示重复单元的数量。 2.透明度&#…

掌握 gRPC 和 RPC 的关键区别

一、远程过程调用协议简介 1、RPC 的本质 首先&#xff0c;我们探讨一下什么是 RPC。RPC&#xff0c;缩写为 Remote Procedure Call Protocol&#xff0c;直译来看就是远程过程调用协议。 讲得通俗一些&#xff1a; RPC 是一种通信机制RPC 实现了客户端/服务器通信模型 官…

【大厂秘籍】 - Redis持久化篇

创作不易&#xff0c;你的关注分享就是博主更新的最大动力&#xff0c; 每周持续更新 微信搜索【 企鹅君】关注还能领取学习资料喔&#xff0c;第一时间阅读(比博客早两到三篇) 求关注❤️ 求点赞❤️ 求分享❤️ 对博主真的非常重要 企鹅君原创&#xff5c;GitHub开源项目gith…

【算法】信使(最短路问题)

题目 战争时期&#xff0c;前线有 n 个哨所&#xff0c;每个哨所可能会与其他若干个哨所之间有通信联系。 信使负责在哨所之间传递信息&#xff0c;当然&#xff0c;这是要花费一定时间的&#xff08;以天为单位&#xff09;。 指挥部设在第一个哨所。 当指挥部下达一个命令…

<软考高项备考>《论文专题 - 63 质量管理(2) 》

2 过程1-规划质量管理 2.1 问题 4W1H过程做什么识别项目及其可交付成果的质量要求、标准&#xff0c;并书面描述项目将如何证明符合质量要求、标准的过程&#xff1b;作用&#xff1a;为在整个项目期间如何管理和核实质量提供指南和方向为什么做1、识别项目/产品质量要求和标…

debian 11 arm64 aarch64 D2000 平台编译 box86 box64 笔记

参考资料 https://github.com/ptitSeb/box86/blob/master/docs/COMPILE.md 源码地址 GitHub - ptitSeb/box86: Box86 - Linux Userspace x86 Emulator with a twist, targeted at ARM Linux devices deb在线源地址&#xff08;打不开&#xff09;&#xff1a; Itais box86…

两个阅读英文论文的免费AI工具

大家好啊&#xff0c;我是董董灿。 本文会介绍我平时用到的两个免费的基于GPT的论文阅读平台&#xff0c;很好用&#xff0c;对于有英文阅读困难症的小伙伴(比如我)是真的提效。 1、 英文阅读困难症 在我的工作以及业余学习中&#xff0c;会时不时的需要翻看一些英文论文&…

GitLab任意用户密码重置漏洞(CVE-2023-7028)

GitLab CVE-2023-7028 POC user[email][]validemail.com&user[email][]attackeremail.com 本文链接&#xff1a; https://www.黑客.wang/wen/47.html

JavaScript保留字和预定义的全局变量及函数汇总

保留字也称关键字&#xff0c;每种语言中都有该语言本身规定的一些关键字&#xff0c;这些关键字都是该语言的语法实现基础&#xff0c;JavaScript中规定了一些标识符作为现行版本的关键字或者将来版本中可能会用到的关键字&#xff0c;所以当我们定义标识符时就不能使用这些关…

【Git】本地仓库文件的创建、修改和删除

目录 一、基本信息设置 1、设置用户名2、设置用户名邮箱 二、Git仓库操作介绍 1、创建一个新的文件夹2、在文件内初始化git仓库&#xff08;创建git仓库&#xff09;3、向仓库中添加文件 1.创建一个文件2.将文件添加到暂存区3.将暂存区添加到仓库 4、修改仓库文件 1.修改文件2.…

汽车级线性电压稳压器LM317MBSTT3G:新能源汽车的理想之选

LM317MBSTT3G是一款可调三端子正向线性稳压器&#xff0c;能够在 1.2 V 至 37 V 的输出电压范围内提供 500 mA 以上的电流。此线性电压稳压器使用非常简便&#xff0c;仅需两个外部电阻即可设置输出电压。另外&#xff0c;它采用内部电流限制、高温关断和安全区域补偿&#xff…

边缘计算:连接实时数据的力量与未来发展之路

边缘计算是一种分布式计算范式&#xff0c;它旨在将数据处理、存储和应用服务带到数据源的近端&#xff0c;即网络的“边缘”。在边缘计算模型中&#xff0c;算力和存储资源距离末端用户或数据源更近&#xff0c;这减少了数据在网络中传输的距离&#xff0c;从而降低延迟&#…

【Web】token机制

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Web ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 机制基本&#xff1a; 优势&#xff1a; 结语 我的其他博客 前言 在当今互联网时代&#xff0c;安全、高效的用户身份验证和资源授…

关于Python里xlwings库对Excel表格的操作(三十二)

这篇小笔记主要记录如何【如何使用“Chart类”、“Api类"和“Axes函数”设置坐标轴标题文本内容】。 前面的小笔记已整理成目录&#xff0c;可点链接去目录寻找所需更方便。 【目录部分内容如下】【点击此处可进入目录】 &#xff08;1&#xff09;如何安装导入xlwings库…

Sip - Ubuntu 配置 miniSIPServer 服务器(测试用)

客户提供的账号过期了&#xff0c;简单搭建 SIP 服务器&#xff0c;以便测试使用。个人认为这个配置起来最为简单&#xff0c;且测试功能足够。 官网miniSIPServer - 基于 Windows 以及 Linux 平台的 VoIP (SIP) 服务器软件. miniSIPServer 可能是最容易使用的 VoIP(SIP) 服务器…

【国产mcu填坑篇】华大单片机(小华半导体)一、SPI的DMA应用(发送主机)HC32L136

最近需要用华大的hc32l136的硬件SPIDMA传输&#xff0c;瞎写很久没调好&#xff0c;看参考手册&#xff0c;瞎碰一天搞通了。。。 先说下我之前犯的错误&#xff0c;也是最宝贵的经验&#xff0c;供参考 没多看参考手册直接写&#xff08;即使有点烂仍然提供了最高的参考价值。…