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

动态规划

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

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

数学归纳法:

  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.mzph.cn/news/619840.shtml

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

相关文章

关于jupyter突然打不开的问题

好久没有用python了&#xff0c;我的电脑环境是安装过anaconda和pycharm&#xff0c;但是有些简单的东西就希望在jupyter中测试一下&#xff0c;但是最近发现jupyter打不开了。 具体是&#xff1a; 在这里打开jupyter是可以的&#xff0c;但是在命令行就不行&#xff0c;表现为…

计算机毕业设计 | SpringBoot+vue的家庭理财 财务管理系统(附源码)

1&#xff0c;绪论 1.1 项目背景 网络的发展已经过去了七十多年&#xff0c;网络技术的发展&#xff0c;将会影响到人类的方方面面&#xff0c;网络的出现让各行各业都得到了极大的发展&#xff0c;为整个社会带来了巨大的生机。 现在许多的产业都与因特网息息相关&#xff…

Python实现华容道问题详解

概要 华容道是一种古老的中国益智游戏&#xff0c;最早出现在中国的《千古文馆》中。这个游戏的目标是将一块特殊的方块从一个方形的棋盘中移出&#xff0c;通过滑动其他的方块来达到这个目标。本文将介绍如何使用Python来实现华容道问题&#xff0c;并提供详细的代码示例。 游…

飞鱼CRM接入第三方系统 飞鱼API对接详细教程

场景描述 在白码低代码开发平台中&#xff0c;是支持外部crm系统的线索通过接口流入到白码系统里面&#xff0c;换而言之&#xff0c;只要外部的系统有线索api接口&#xff0c;白码系统可以接收线索并在白码系统上进行后续操作。本文以飞鱼crm系统为例&#xff0c;讲解如何接收…

学习就要从简单的开始嘛,开始学一个个人博客吧

做一个个人博客第一步该怎么做&#xff1f; 好多零基础的同学们不知道怎么迈出第一步。 那么&#xff0c;就找一个现成的模板学一学呗&#xff0c;毕竟我们是高贵的Ctrl c v 工程师。 但是这样也有个问题&#xff0c;那就是&#xff0c;那些模板都&#xff0c;太&#xff01;…

Linux/Haystack

Enumeration nmap 还是先看看端口对外开放情况&#xff0c;对外开放了22,80,9200 22应该是ssh&#xff0c;80是web&#xff0c;9200不知道是什么 TCP/80 访问web&#xff0c;没有什么链接&#xff0c;只有一张图片 有可能图片中有些东西&#xff0c;但还是尝试扫描一下网站…

ptaR7-6/zzuli2106 有去有回

题目 输入n个整数&#xff0c;第一趟按从左到右间隔k个数取数据&#xff0c;然后第二趟再从右到左间隔k-1个数取余下的数&#xff0c;如果数据没有取完&#xff0c;下一趟再间隔k-2个从左到右取数据&#xff0c;如此反复&#xff0c;直到所有的数据取完为止。注意&#xff1a;…

《SRE Google 运维解密》笔记

指导思想 介绍 Google 生产环境介绍 borg 是 k8s 的前身。 拥抱风险 服务质量 现在的 SLO 没有更细粒度的划分到季度 如果划分到季度&#xff0c;需要用这个数据来限制什么或者进行什么活动&#xff1f; L1S 链路的 SLA 的签署工作已经做了很多 对于 SLA 的达成情况数据没有…

Iceberg从入门到精通系列之十九:分区

Iceberg从入门到精通系列之十九&#xff1a;分区 一、认识分区二、Iceberg的分区三、Hive 中的分区四、Hive 分区问题五、Iceberg的隐藏分区六、分区变换七、分区变换 一、认识分区 分区是一种通过在写入时将相似的行分组在一起来加快查询速度的方法。 例如&#xff0c;从日志…

基于springboot生鲜交易系统源码和论文

首先,论文一开始便是清楚的论述了系统的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了系统的需求基础上需要进一步地设计系统,主要包括软件架构模式、整体功能模块、数据库设计。本项…

IPv6路由协议---IPv6动态路由(OSPFv3-3)

OSPFv3使用Link-local地址 OSPFv3是运行在IPv6上的路由协议,同样使用链路本地地址来维持邻居,同步LSA数据库。除Vlink外的所有OSPFv3接口都使用链路本地地址作为源地址及下一跳来发送OSPFv3报文,带来的好处: 不需要配置IPv6全局地址,就可以得到OSPFv3拓扑,实现拓扑与地址…

【职工管理系统(C++版)】

一、管理系统需求 职工管理系统可以用来管理公司内所有员工的信息。 本次主要利用C来实现一个基于多态的职工管理系统。 公司中职工分为三类:普通员工、经理、老板&#xff0c;显示信息时&#xff0c;需要显示职工编号、职工姓名、职工岗位、以及职责。 普通员工职责:完成经…

Nacos和Eureka比较、统一配置管理、Nacos热更新、多环境配置共享、Nacos集群搭建步骤

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Nacos和eureka的对比二、统一配置管理二、Nacos热更新方式一方式二 三、多环境配置共享四、Nacos集群搭建步骤&#xff08;黑马springCloud的p29&#xff0…

springboot邮件发送

一、讲一下thymeleaf的简单使用 1.在根路径下写一个 resources/templates/good.html 文件 2. 在代码实现 记得 不要加 RequestBody这个注解&#xff0c;因为它会把string当作普通 的字符串&#xff0c;而不是去渲染对应的good.html视图 3.看效果 关于图片的显示&#xff0…

黑马程序员 Docker笔记

本篇学习笔记文档对应B站视频&#xff1a; 同学们&#xff0c;在前两天我们学习了Linux操作系统的常见命令以及如何在Linux上部署一个单体项目。大家想一想自己最大的感受是什么&#xff1f; 我相信&#xff0c;除了个别天赋异禀的同学以外&#xff0c;大多数同学都会有相同的…

【动态规划】【滑动窗口】C++算法:3003 执行操作后的最大分割数量

作者推荐 【动态规划】【字符串】扰乱字符串 本文涉及的基础知识点 C算法&#xff1a;滑动窗口总结 动态规划 LeetCode3003 执行操作后的最大分割数量 给你一个下标从 0 开始的字符串 s 和一个整数 k。 你需要执行以下分割操作&#xff0c;直到字符串 s 变为 空&#xff1…

四大会计假设

目录 一. 会计主体假设二. 持续经营假设三. 会计期间假设四. 货币计量假设 \quad \quad 一. 会计主体假设 \quad 会计主体: 会计工作为其服务的特定单位或组织。 会计主体的定义 1.具有一定数量的资金。 2.进行独立的生产经营或其他活动。 3.实行独立核算。 \quad 会计主体假设…

leetcode 125. 验证回文串

题目&#xff1a; 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xff0c;如果它是 回文串 &#xff0c;返回 true &…

阅读文献-胃癌

写在前面 今天先不阅读肺癌的了&#xff0c;先读一篇胃癌的文章 文献 An individualized stemness-related signature to predict prognosis and immunotherapy responses for gastric cancer using single-cell and bulk tissue transcriptomes IF:4.0 中科院分区:2区 医学…

JFinal综合信息管理系统

项目地址&#xff1a;mendianyu/AdvancedManagement: 综合信息管理系统 (github.com) 项目演示地址&#xff1a;软件构造大作业演示视频_哔哩哔哩_bilibili 项目功能 一&#xff1a;基于Jfinal构建信息管理系统&#xff0c;要求包含用户管理&#xff0c;翻译业务模块管理&…