算法——动态规划

1. 什么是动态规划?

动态规划(Dynamic Programming)是一种解决多阶段决策问题的优化方法。它通常用于解决具有重叠子问题和最优子结构性质的问题,能够将一个大问题分解为多个重叠的子问题,并通过存储子问题的解来避免重复计算,从而提高算法效率。

动态规划的基本思想是将原问题分解为若干子问题,先求解子问题的解,然后将这些子问题的解组合起来,逐步推导出原问题的解。为了避免重复计算,动态规划算法通常采用表格(数组)来存储已经求解的子问题的解,这种表格通常称为动态规划(dp)表。 

2. 动态规划算法的解题流程

动态规划算法的一般步骤如下:

  1. 定义状态: 明确定义问题的状态,将原问题转化为具有重叠子问题的子问题。在解题中体现为确认dp表中每一个格子表示什么。

  2. 找到状态转移方程: 建立子问题之间的递推关系,通过状态转移方程描述问题的最优子结构。在解题中体现为dp表如何去填写。

  3. 初始化: 初始化动态规划表,将边界状态的值填入表中。在解题中,初始化的目的是为了保证最后得到的结果是正确的。

  4. 逐步计算: 从边界状态开始,按照状态转移方程逐步计算并填充动态规划表。在解题中,体现为确认填dp表的方向。

  5. 解读结果: 根据动态规划表中的结果得到原问题的解。在解题中,体现为返回正确结果。

3. 应用实例

①斐波那契数列模型

1. 第N个泰波那契数

题目链接:1137. 第 N 个泰波那契数 - 力扣(LeetCode)

解析:看完这道题,我们分析这个题目可以发现,题目已经将几乎动态规划的所有步骤告诉了我们,我们只需要按照他说的完成流程即可,我们定义dp[i]表示第i个泰波那契数,我们的动态转移方程为 dp[i] = dp[i-1] + dp[i-2] + dp[i-3],而初始化要想得到正确结果,我们需要将dp[0] = 0, dp[1] = dp[2] = 1,填表方向则是从左向右从第3个位置开始填,最后返回dp[n]即可(n为0,1,2时需要进行特殊判断),代码如下

class Solution 
{
public:
    int tribonacci(int n) 
    {
        if (n == 0) return 0;
        if (n <= 2) return 1;
        vector<int> dp(n+1);
        dp[1] = dp[2] = 1;

        for (int i = 3; i <= n; i++)
            dp[i] = dp[i-1] + dp[i-2] + dp[i-3];

        return dp[n];
    }
};

2. 三步问题

题目链接:面试题 08.01. 三步问题 - 力扣(LeetCode)

解析:分析这个题目,我们可以创建一个大小为(n+1)的dp表,我们定义dp[i]为到小孩上到第i个阶梯共有多少种方式,根据题目我们可以发现,要想到达dp[i]这个位置,我们有三种方法上来,分别是从前三个位置上来,即

所以我们可以得到dp[i]的动态转移方程:dp[i] = dp[i-1] + dp[i-2] + dp[i-3],初始化时,只有前三个台阶需要特殊处理,由于一开始就处于第0个台阶因此dp[0]=1,到第一个台阶只有一种方法,所以dp[1] = 1,到第二个台阶有2种方法,所以dp[2] = 2,由于题目中是从下往上跳,因此填表顺序为从第三个台阶开始从左向右填,最终返回dp[n]即可,代码如下

class Solution 
{
public:
    int waysToStep(int n) 
    {
        if (n <= 1) return 1;
        if (n == 2) return 2;

        int mod = 1e9 + 7;
        vector<long long> dp(n+1);
        dp[0] = dp[1] = 1;
        dp[2] = 2;

        for (int i = 3; i <= n; i++)
            dp[i] = (dp[i-1] + dp[i-2] + dp[i-3]) % mod;
        
        return dp[n];
    }
};

3. 使用最小花费爬楼梯

题目链接:746. 使用最小花费爬楼梯 - 力扣(LeetCode)

解析:分析题目,我们可以定义dp[i]表示到第i个阶梯的最小花费,而要想到达第i个阶梯,要么只能从第i-1个阶梯来,要么只能从i-2个阶梯来,我们只需要最小的花费,所以动态转移方程如下 dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);,由于可以选择下标从0或1的阶梯开始爬,因此我们无需初始化,填表顺序为从前往后填,最后返回dp[n]即可,代码如下

class Solution 
{
public:
    int minCostClimbingStairs(vector<int>& cost) 
    {
        int n = cost.size();
        vector<int> dp(n+1);

        for(int i = 2; i <= n; i++)
            dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);

        return dp[n];
    }
};

除了从左向右填表外,我们还可以从右向左填表,定义dp[i]表示从第i个阶梯开始到达楼顶的最小花费,从第i个阶梯向后移动,要么只能移动一步要么只能移动两步,我们需要得到其中较小的一种情况,因此有 dp[i] = min(dp[i+1], dp[i+2]) + cost[i],由于从倒数第一和倒数第二个阶梯都能直接到达楼顶,因此我们需要初始化dp[n-1] = cost[n-1], dp[n-2] = cost[n-2], 填表时从后往前填即可,最终返回dp[0]与dp[1]中的较小值,代码如下

class Solution 
{
public:
    int minCostClimbingStairs(vector<int>& cost) 
    {
        int n = cost.size();
        vector<int> dp(n+1);
        dp[n-1] = cost[n-1];
        dp[n-2] = cost[n-2];

        for(int i = n - 3; i >= 0; i--)
            dp[i] = min(dp[i+1], dp[i+2]) + cost[i];

        return min(dp[0], dp[1]);
    }
};

4. 解码方法

题目链接:91. 解码方法 - 力扣(LeetCode)

解析:分析这个题目,我们可以定义dp[i]表示到达第i个字符时共有多少种解码方法,若当前单个字符能够解码(不为'0'),则表明当前字符是一种解码方法,此时该字符可以与前面的串形成一种编码即dp[i-1],若当前字符不能够解码(为'0'),则表明当前字符不是一种解码方式,此时dp[i]就置为0;若当前字符能与前一个字符进行解码,则表明这两个字符是一种解码方法,即dp[i-2],若不能形成,则dp[i]置于0,对于初始化我们只需要知道第一个字符是否为'0'即可,是的话dp[0] = 0,不是的话dp[0] = 1,dp[1]有0,1,2三种情况,填表顺序为从左往右填,最终返回dp[n-1]即可,代码如下

class Solution 
{
public:
    int numDecodings(string s) 
    {
        int n = s.size();
        vector<int> dp(n);
        if (s[0] != '0') dp[0] = 1;
        if (n == 1) return dp[0];

        int code = stoi(s.substr(0, 2));
        if (s[1] != '0' && s[0] != '0') dp[1]++;
        if (code >= 10 && code <= 26) dp[1]++; 
            
        for (int i = 2; i < n; i++)
        {
            if (s[i] != '0') dp[i] += dp[i-1];
            code = stoi(s.substr(i-1, 2));
            if (code >= 10 && code <= 26) dp[i] += dp[i-2];
        }

        return dp[n-1];
    }
};

②路径问题

1. 不同路径

题目链接:62. 不同路径 - 力扣(LeetCode)

解析:分析题目,我们可以规定dp[i][j]表示到达(i, j)位置的所有路径数,由于机器人每次只能向下或者向右移动,因此对于一个dp[i][j]它只能从左侧过来,或者从上方下来,即 dp[i][j] = dp[i-1][j] + dp[i][j-1],为了免除边界处理的情况,我们可以人为的为dp表填上一行,防止i-1与j-1越界,即直接初始化dp大小为(m+1)*(n+1),此时要注意,由于我们添加了一行因此原数组的位置的映射关系发生了改变,即dp[i][j]表示的是到达(i-1, j-1)位置的路径数,为了保证结果的正确我们可以挑选dp[0][1] = 1 或者 dp[1][0] = 1,填表顺序为从左向右,从上至下,最后返回dp[m][n]即可,代码如下

class Solution 
{
public:
    int uniquePaths(int m, int n) 
    {
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        dp[0][1] = 1;

        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= n; j++)
                dp[i][j] = dp[i-1][j] + dp[i][j-1];

        return dp[m][n];
    }
};

2. 不同路径Ⅱ

题目链接:63. 不同路径 II - 力扣(LeetCode)

解析:这道题整体的思路与上一题类似,但是这道题在遇见1的时候是到达不了这个地方的,此时将该位置置为0即可(注意下标间的映射为dp[i][j]对应ob[i-1][j-1]),代码如下

class Solution 
{
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) 
    {
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        dp[0][1] = 1;

        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= n; j++)
                if (obstacleGrid[i-1][j-1] == 1) dp[i][j] = 0;
                else dp[i][j] = dp[i-1][j] + dp[i][j-1];

        return dp[m][n];
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot+Mybatis-plus+shardingsphere实现分库分表

SpringBootMybatis-plusshardingsphere实现分库分表 文章目录 SpringBootMybatis-plusshardingsphere实现分库分表介绍引入依赖yaml配置DDL准备数据库ds0数据库ds1 entitycotrollerserviceMapper启动类测试添加修改查询删除 总结 介绍 实现亿级数据量分库分表的项目是一个挑战…

小白跟做江科大51单片机之DS1302按键可调时钟

1.引入上一个程序的代码 2.引入Key和Timer0文件 3.获取按键值 定义全局变量unsigned char keynum main函数中 keynumKey(); 4.设置第一个按键的两种模式&#xff0c;以此来控制时钟的设定和显示 if(keynum1) { if(MODE0) { …

GDB调试入门笔记

文章目录 What&#xff1f;WhyHow安装GDB安装命令查看是否安装成功调试简单的程序预备一个程序调试 使用breakinfolistnextprintstep一些小技巧在gdb前shell日志功能watch point| catch point 调试core调试一个运行的程序 What&#xff1f; GDB是什么&#xff1f; 全称GNU sym…

lowcode-engine接入编辑器

https://lowcode-engine.cn/site/docs/guide/create/useEditor 方案1 pnpm init pnpm add "alilc/create-elementlatest"pnpm create "alilc/element" editor-project-name选择编辑器 进入执行pnpm install命令安装包 pnpm start报错 pnpm add &qu…

JMeter VS RunnerGo :两大主流性能测试工具对比

说起JMeter&#xff0c;估计很多测试人员都耳熟能详。它小巧、开源&#xff0c;还能支持多种协议的接口和性能测试&#xff0c;所以在测试圈儿里很受欢迎&#xff0c;也是测试人员常用的工具&#xff0c;不少企业也基于JMeter建立起自己的自动化测试能力&#xff0c;提升工作效…

leetcode 经典题目42.接雨水

链接&#xff1a;https://leetcode.cn/problems/trapping-rain-water 题目描述 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 思路分析 首先&#xff0c;我们需要遍历数组&#xff0c;对于每个元素&am…

链路负载均衡之策略路由

一、策略路由的概念 一般来说&#xff0c;防火墙是根据目的地址查看路由&#xff0c;这种情况下只能根据报文的目的地址为用户提供服务&#xff0c;没办法更加灵活对内网用户进行区分&#xff0c;让不同用户流量走不同的链路转发&#xff0c;如根据源地址、应用协议等区分流量…

3.3改造from框

1.如何解决如何导入组件 2.导入组件如何传值 我们如何区分哪个父组件那个子组件我们如何区分 我们现在只知道我们导入的组件&#xff0c;导入的组件是父组件还是子组件 看一下专业回答 如何进行传值的方式 父组件穿的通过是 v-bind的方式 子组件是通过defineProps接受的方…

如何构建用于物体和标志检测的自定义模型

让我们快速了解一下AWS的机器学习技术栈&#xff0c;它几乎提供了解决我们业务问题所需的所有机器学习方面的支持。 物体检测是什么&#xff1f; 物体检测是从图像或视频帧中检测特定类别实例的任务。我们的目标是在图像/视频帧中找出哪里有什么物体。它是其他依赖物体的任务…

基于单片机的室内空气质量监控系统设计

目 录 摘 要 I Abstract II 引 言 1 1 控制系统设计 3 1.1 方案选择 3 1.2 系统控制原理 4 2系统硬件设计 5 2.1 单片机的选择与设计 5 2.2 温湿度模块设计 6 2.3 甲醛采集模块设计 8 2.4 显示器模块设计 9 2.5 按键模块设计 10 2.6 报警模块设计 11 2.7 加湿及风扇模块设计 1…

【JavaEE】_Spring MVC项目之使用对象传参

目录 1. 使用对象传参 2. 后端参数重命名问题 2.1 关于RequestParam注解 本专栏关于Spring MVC项目的单个及多个参数传参一文中&#xff0c;已经介绍过了对于不同个数的参数传参问题&#xff0c;原文链接如下&#xff1a; 【JavaEE】_Spring MVC 项目单个及多个参数传参-CS…

部署LVS集群之DR模式

直接路由模式----DR模式 理念&#xff1a; 直接路由&#xff08;是lvs的默认模式&#xff09; DR模式和隧道模式唯一的区别&#xff1a;dr模式这四台服务器在同一网段&#xff0c;隧道模式 &#xff1a;这四台服务器不在同一网段 客户端 ------->代理服务器------->真实…

Linux命令之top命令

目录 语法 参数说明&#xff1a; 显示信息 top 命令的一些常用功能和显示信息&#xff1a; 第一行&#xff1a;系统负载信息 第二行&#xff1a;进程信息 进程列表 总体系统信息&#xff1a; 进程信息&#xff1a; 功能和交互操作&#xff1a; Linux top 是一个在 L…

JavaBoy假期如何学习项目?弯道块才是真的快!

至暗时刻 老话说的好&#xff0c;弯道快才是真的快&#xff0c;谁直线不会加油&#xff1f;每到假期都是在座的各位弯道超车的时候。转眼自己已经出来搬了快四年砖头了&#xff0c;偶尔访问下牛客发现行情真是一年不如一年。。。不由得回想起自己春招时候的经历。 回想起2020年…

Spring基础——方法注入(Method Injection)

目录 查找方法注入&#xff08;Lookup Method&#xff09;查找方法注入基于XML的方法注入基于注解的方法注入 Arbitrary Method Replacement&#xff08;任意方法替换&#xff09; 文章所用项目源码参考&#xff1a;java_spring_learn_repo 查找方法注入&#xff08;Lookup Met…

高分辨率全球海洋温度和盐度再分析数据Global Ocean Physics Reanalysis(0.083°),并利用matlab读取绘图

1.引言 在研究全球海平面变化的问题中&#xff0c;卫星测高获得总的海平面变化&#xff0c;而海平面变化包含质量变化和比容变化。因此测高数据和海洋物理分析数据对于海平面研究至关重要。 测高数据下载网址&#xff1a; Global Ocean Gridded L 4 Sea Surface Heights And …

厉害了,有了这款AI智能代码助手,摸鱼新纪元来了!

大家好啊&#xff0c;欢迎来到web测评。本期给大家分享一款AI智能代码助手BaiduComate&#xff0c;让大家上班有更多的时间来摸鱼&#xff0c;俗话说的好&#xff0c;摸鱼一时爽&#xff0c;一直摸一直爽啊哈哈~~ 智能代码助手使用地址 前往BaiduComatehttps://comate.baidu.c…

3d模型怎么镜像?3d模型镜像的步骤---模大狮模型网

在3D建模软件中&#xff0c;对3D模型进行镜像操作通常是指沿着某个轴线(如X、Y、Z轴)进行镜像翻转&#xff0c;使模型在该轴线的一侧产生对称的镜像效果。以下是在常见的3D建模软件中对3D模型进行镜像的一般步骤&#xff1a; 3d模型镜像步骤&#xff1a; 选择模型&#xff1a;…

碳视野|全国首个ESG区域行动方案通过,上海政府推进ESG有八“要”

引领绿色转型&#xff0c;共筑低碳未来&#xff01;AMT企源碳管理团队深入解读碳领域政策、概念及标准&#xff0c;分享实践经验&#xff0c;助力产业绿色发展。我们启动“碳视野、碳课堂、碳实践”三大专栏&#xff0c;紧跟碳行业政策动态&#xff0c;以“科普实践分享”为核心…

小程序管理平台:助力企业数字化转型

微信小程序生态近年来发展迅猛&#xff0c;已成为中国互联网不可忽视的力量。截至2023年6月&#xff0c;微信小程序数量已超过300万&#xff0c;同比增长25%&#xff0c;涵盖了电商、生活服务、教育、金融等众多行业。微信小程序内容生态已经日趋完善&#xff0c;并满足各领域用…