动态规划入门经典问题讲解

news2024/9/23 5:26:31

最近开始接触动态规划问题,以下浅谈(或回顾)一下这些问题的求解过程。

解题思路

对于动态规划问题,由于最终问题的求解需要以同类子问题作为基础,故需要定义一个dp数组(一维或二维)来记录问题求解的各个状态(避免多次求算重复子问题);然后就是确认状态转移方程,也就是问题求解的递推公式;由于问题的最终状态需要从最初状态递推而来,故需初始化状态,即初始化dp数组。

步骤如下:

  1. 确定dp数组以及下标的含义

  1. 确定递推公式

  1. dp数组如何初始化

  1. 确定遍历顺序

  1. 举例推导dp数组

(上述步骤来自代码随想录)

爬楼梯问题

爬楼梯时每一次只能上1级或2级阶梯,问爬n级阶梯有多少种方法?

这是一个最简单的动态规划问题,以下是解题步骤:

  1. 定义数组int dp[1005],根据问题的问法,设dp[n]表示爬n级楼梯时的方法总数;

  1. 确认状态转移方程,我们直接考虑最后一次爬上n级楼梯的方法。显然,最后一次无非直接爬1级阶梯上到第n级,或者爬2级阶梯上到了第n级。由于前者是发生在已爬n-1级阶梯的基础上,而后者发生在已爬n-2级阶梯的基础上。故爬n级阶梯的方法总数等于dp[n-1]+dp[n-2],即转台转移方程:dp[n] = dp[n-1]+dp[n-2];

  1. 确定初始状态,显然dp[n]需要从两个子状态推导而来,故问题的边界为,确认dp[1]与dp[2].易知,dp[1]=1,dp[2]=2;

  1. 确定遍历顺序,显然需要从dp[1]与dp[2]往后递推。

第一种情况

第二种情况

(图来自小灰漫画)

#include<iostream>
using namespace std;

//dp[i]表示爬i级阶梯时所花费的步数
int dp[1005];
int main() {
    int n;
    cin >> n;
    //初始化
    dp[1] = 1;
    dp[2] = 2;
    //递推公式为:dp[i] = dp[i-1]+dp[i-2] (i>=3)
    for (int i = 3;i <= n;i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    cout << dp[n] << endl;
    return 0;
}

求最大子段

问题描述:给定一个数字序列,称由连续元素组成的序列为该数字序列的子段,问子段元素之和的最大值为何?

由于每一个子段必有一个前缀与后缀,最大子段必有一个前缀或者后缀,我们干脆定义dp[i]表示以序列中第i个元素为后缀的最大子段;定义int a[1005]存储数字序列的各个元素,a[i]表示序列中的第i个元素。解题步骤如下:

  1. 定义int dp[1005],dp[i]表示以序列中第i个元素为后缀的最大子段;

  1. 确认状态转移方程,每一个元素可以单独作为一个子段,因此对于dp[i]而言,其最大子串无非两种情况:第一,若dp[i-1]>0,那么a[i]单独作为子段,其必定小于第i元素与以序列中第i-1个元素为后缀的最大子段拼接所得到得新子段,此时dp[i] = dp[i-1]+a[i];若dp[i-1]<=0,那么a[i]单独作为子段会使dp[i]更大,故dp[i]=a[i]。即转移方程为:dp[i]=max(dp[i-1]+a[i],a[i]);

  1. 确认初始状态,显然获取dp[i]需要得知dp[i-1],即dp[1]=a[1]

  1. 确定遍历顺序,显然从左往右扫描即可。

#include<iostream>
using namespace std;
const int maxn = 1005;
int a[maxn];
int dp[maxn];

int main() {
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++) {
        cin >> a[i];
    }
    dp[1] = a[1];
    for (int i = 2;i <= n;i++) {
        dp[i] = max(dp[i - 1] + a[i], dp[i]);
    }
    int max = dp[1];
    for (int i = 2;i <= n;i++) {
        if (max < dp[i]) {
            max = dp[i];
        }
    }
    cout << max << endl;
    return 0;
}

求最长上升子序列

问题描述:给定一个数字序列,取其中的部分元素(元素无需连续),要求元素按升序排列,问上升子序列的最大长度,也就是该子序列里面元素的最大个数。

依旧定义int dp[1005],其中dp[i]表示以序列中第i个元素为结尾的最长上升子序列。对于dp[i]最长上升子序列与后面元素有关,若a[i]>a[i-1]那么必定有dp[i]=dp[i-1]+1,可在a[i]后面的元素中,dp[i-1]并不一定就是最大的,依旧需要遍历dp[1]~dp[i-1]中,满足a[j]<a[i]且a[j]最大的那个上升子序列,从而接到a[i]后面,解题思路如下:

  1. 定义int dp[1005],dp[i]表示以序列中第i个元素为结尾的最长上升子序列;

  1. 确认递推公式,dp[i] = max(dp[i-1],dp[i-2],..........,dp[1])+1;

  1. 确认初始化状态,显然每一个元素的都至少可以单独构成一个长度为1的最长上升子序列,从而设置dp[0]=0,a[0]=-inf (inf表示无穷大),保证序列中每一个元素都至少能大于a[0];

  1. 确认遍历顺序,依旧是从左往右扫描。

#include<iostream>
using namespace std;

const int maxn = 1005;
int a[maxn];
int dp[maxn];
const int inf = 0xffffff;

//求最长子序列,假设dp[i]表示以第i元素为结尾的最长上升子序列

int main(){
    int n;
    cin >> n;
    a[0] = -inf;
    for (int i = 1;i <= n;i++) {
        cin >> a[i];
    }
    int ans = 0;
    for (int i = 1;i <= n;i++) {
        for (int j = 0;j < i;j++) {
            if (a[i] > a[j]) {
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        ans = max(ans, dp[i]);
    }
    cout << ans << endl;
    return 0;
}

求最大公共子串

问题描述:给定两个字符串a,b,求a与b中公共部分的元素个数.例如:a="abfed",b="bfd",那么最大公共子串ps = "bfd",其元素个数为3.

此时假定一个二维数组int dp[1005][1005],那么dp[i][j]表示a前i个字符构成的子串与b前j个字符构成的子串的最大公共子串。那么此时若a[i-1]==b[j-1],说明字符串a的第i个字符与字符串b的第j个字符相等,那么此时dp[i][j]=dp[i-1][j-1]+1;若a[i-1]!=b[j-1],dp[i][j]=max(dp[i-1][j],dp[i][j]),因为父串a与其他串b的最大子串一定大于或等于该父串a的子串与其他串b的最大子串.

解题思路:

  1. 定义int dp[1005][1005],dp[i][j]表示a前i个字符构成的子串与b前j个字符构成的子串的最大公共子串

  1. 确认递推公式,若a[i-1]==b[j-1],则dp[i][j]=dp[i-1][j-1]+1;否则,dp[i][j]=max(dp[i-1][j],dp[i][j]).

  1. 确认初始化状态,只需要初始化dp[0][0]=0即可。

  1. 确认遍历顺序,依旧是从左往右,从上往下扫描。

#include<iostream>
#include<string>
#include<cstdio>

using namespace std;
int dp[105][105];
int main() {
    string a, b;
    cin >> a >> b;
    int lena = a.length();
    int lenb = b.length();
    memset(dp, 0, sizeof(dp));
    for (int i = 1;i <= lena;i++) {
        for (int j = 1;j <= lenb;j++) {
            if (a[i - 1] == b[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            }
            else {
                dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);
            }
        }
    }
    cout << dp[lena][lenb] << endl;
    return 0;
}

求编辑距离

问题描述:给定一个字符串S与一个模板字符串T,可以对S进行插、替、删三种操作,问S经过上述操作变为T的最少次数,即为最小编辑距离。

依旧设int dp[1005][1005],其中dp[i][j]表示S的前i个字符与T的前j个字符的最小编辑距离。

解题思路:

  1. 定义int dp[1005][1005],dp[i][j]表示S的前i个字符与T的前j个字符的最小编辑距离;

  1. 确认递推公式,若a[i-1]==b[j-1],则dp[i][j]=dp[i-1][j-1];否则,在dp[i-1][j-1]、dp[i][j-1]、dp[i-1][j]中,若dp[i-1][j-1]最小说明需要将a[i-1]替换为b[j-1];若dp[i][j-1]最小,需要在S的前i个字符后面添加一个b[j-1];若dp[i-1][j]最小,需要删除a[i-1]。即dp[i][j]=min(dp[i][j-1],dp[i-1][j],dp[i-1][j-1])+1;

  1. 确认初始化状态,需要依次初始化dp[0][0]~dp[0][S.length()]以及dp[T.length()][0];

  1. 确认遍历顺序,依旧是从左往右,从上往下扫描。

#include<iostream>
#include<string>
using namespace std;

int dp[105][105];

//dp[i][j]表示S前i个字符与T前j个字符编辑时的最小距离

//求编辑距离
int func(string S,string T) {
    int lenS;
    int lenT;
    lenS = S.length();
    lenT = T.length();
    dp[0][0] = 0;
    for (int i = 1;i <= lenS;i++) {
        dp[i][0] = i;
    }
    for (int j = 1;j <= lenT;j++) {
        dp[0][j] = j;
    }
    for (int i = 1;i <= lenS;i++) {
        for (int j = 1;j <= lenT;j++) {
            if (S[i - 1] == T[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            }
            else {
                dp[i][j] = min(min(dp[i - 1][j - 1], dp[i - 1][j]), dp[i][j - 1]) + 1;
            }
        }
    }
    return dp[lenS][lenT];
}

int main() {
    string S, T;
    cin >> S >> T;
    cout << func(S, T) << endl;
    return 0;
}

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

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

相关文章

Vue 3.0 单文件组件 【Vue3 从零开始】

#介绍 在很多 Vue 项目中&#xff0c;我们使用 app.component 来定义全局组件&#xff0c;紧接着用 app.mount(#app) 在每个页面内指定一个容器元素。 这种方式在很多中小规模的项目中运作的很好&#xff0c;在这些项目里 JavaScript 只被用来加强特定的视图。但当在更复杂的…

[Java·算法·中等]LeetCode39. 组合总和

每天一题&#xff0c;防止痴呆题目示例分析思路1题解1分析思路2题解2&#x1f449;️ 力扣原文 题目 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形…

Golang Map实现原理分析与解读

一、map的结构与设计原理 golang中map是一个kv对集合。底层使用hash table&#xff0c;用链表来解决冲突 &#xff0c;出现冲突时&#xff0c;不是每一个key都申请一个结构通过链表串起来&#xff0c;而是以bmap为最小粒度挂载&#xff0c;一个bmap可以放8个kv。在哈希函数的选…

配置临时SSL子域名泛化证书

配置临时SSL子域名泛化证书 三个月有效期第一步&#xff1a;访问SSL证书地址第二步&#xff1a;在华为云上/其他服务器上搜索DNS云解析服务类似的功能第三步&#xff1a;将SSL申请的信息添加到服务器的记录集中第四步&#xff1a;添加完信息进行保存获取key / crt第五步&#x…

蓝桥冲刺31天之第七天

目录 A&#xff1a;三角回文数 B&#xff1a;数数 C&#xff1a;数组切分 D&#xff1a;倍数问题 一星陨落&#xff0c;黯淡不了星空灿烂&#xff1b; 一花凋零&#xff0c;荒芜不了整个春天。 如果命运是世界上最烂的编剧&#xff0c; 你就要争取做人生最好的演员。 即使生…

06_02_Spark Streaming

Spark Streaming 课程目标 说出Spark Streaming的特点说出DStreaming的常见操作api能够应用Spark Streaming实现实时数据处理能够应用Spark Streaming的状态操作解决实际问题独立实现foreachRDD向mysql数据库的数据写入独立实现Spark Streaming对接kafka实现实时数据处理 1、…

打怪升级之CFileDialog类介绍

CFileDialog类 CFileDialog封装用于文件打开操作或文件保存操作的常见对话框。信息来源自Windows官方文档&#xff1a;https://learn.microsoft.com/zh-cn/cpp/mfc/reference/cfiledialog-class?viewmsvc-170 这里重点介绍几个常用的函数功能&#xff1a; 构造函数 explic…

当我在ChatGPT上问重建大师,它居然这样回答我

最近&#xff0c;ChatGPT风靡全球&#xff0c;现象级走红至各大社交媒体。作为最快突破1亿月活量的消费者应用&#xff0c;是怎么样理解重建大师的呢&#xff1f; 今天小编与ChatGPT展开对话&#xff0c;它告诉我&#xff1a; 短短不到一分钟时间&#xff0c;ChatGPT已经概括出…

Grafana 监控面板绘制流程

本篇作者&#xff1a;IoTDB 社区 -- 张洪胤本文以 IoTDB V1.0.1 版本为例本文档介绍了 Apache IoTDB 监控指标通过 Prometheus 的方式进行采集&#xff0c;并且使用 Grafana 的方式进行可视化。1监控指标的 Prometheus 格式说明对于 Metric Name 为 name, Tags 为 K1V1, ..., K…

ABB机器人Offs坐标偏移功能的具体使用方法

ABB机器人Offs坐标偏移功能的具体使用方法 Offs功能说明: 在机器人的工件坐标系中添加一个偏移量 举例说明: 参数及数据类型: 在RobotStudio的仿真操作: 如下图所示,在程序中添加一个移动指令,并记录该点位为p10, 如下图所示,复制该指令语句, 如下图所示,选中…

Qt音视频开发22-音频播放QAudioOutput

一、前言 以前一直以为只有Qt5以后才有QAudioOutput播放音频&#xff0c;其实从Qt4.6开始就有&#xff0c;在Qt6中变成了QAudioSink&#xff0c;功能一样。用QAudioOutput播放音频pcm数据极其方便&#xff0c;只需要指定音频播放设备&#xff08;可能电脑上有多个音频输出设备…

力扣sql简单篇练习(二十六)

力扣sql简单篇练习(二十六) 1 每家商店的产品价格 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # 多行变成多列,考虑用sum if分组 SELECT product_id,sum(IF(storestore1,price,null)) store1,sum(IF(storestore2,price,null)) store2, sum(IF(st…

mmdetectionV2.x版本 训练自己的VOC数据集

mmdetection目录下创建data文件夹&#xff0c;路劲如图所示&#xff0c;不带yololabels 修改配置文件 mmdet/datasets/voc.py 配置图片格式 mmdet/datasets/xml_style.py 如果图片是jpg则改成jpg&#xff0c;是png格式就改成png&#xff0c;这里我不需要改&#xff0c;本…

C++11:包装器

文章目录1. 介绍2. function包装器2.1 介绍2.2 示例1用法2.3 示例22.4 function包装器的功能统一类型简化代码2.5 意义3. bind包装器3.1 介绍3.2 bind包装器的功能绑定固定参数3.3 意义1. 介绍 C 包装器是一种用于给其他编程接口提供更一致或更合适的接口的技术。它可以包装任…

人工智能书籍——《奇点临近》

当人们看到太多相同的时候&#xff0c;也许我们很无知&#xff1b; 当人们看到太多不同的时候&#xff0c;也许我们视野不够大&#xff1b; 当人们同时看到不同和相同的时候&#xff0c;也许这恰是我们的智慧原点。 物质是静止的能量&#xff0c;能量是运动的物质&#xff0c;生…

04 HiveHBase

Hive 一 Hive基本概念 1 Hive简介 学习目标 了解什么是Hive了解为什么使用Hive 1.1 什么是 Hive Hive 由 Facebook 实现并开源&#xff0c;是基于 Hadoop 的一个数据仓库工具&#xff0c;可以将结构化的数据映射为一张数据库表&#xff0c;并提供 HQL(Hive SQL)查询功能&…

05 Spark_Core

01 spark 入门 课程目标&#xff1a; 了解spark概念知道spark的特点&#xff08;与hadoop对比&#xff09;独立实现spark local模式的启动 1.1 spark概述 1、什么是spark 基于内存的计算引擎&#xff0c;它的计算速度非常快。但是仅仅只涉及到数据的计算&#xff0c;并没有涉…

【5】基础语法篇 - VL5 位拆分与运算

VL5 位拆分与运算 1 自己犯的错误 &#xff08;1&#xff09;语法错误 在begin end块 后面加了" ; " case(sel)2b00: begin validout<0; out<0; end;2b01: begin validout<1; out<d0 d1; end;2b10: begin validout<1; out<d0 d2; end;2b11: be…

分享几个常用的运维 shell 脚本

今天咸鱼给大家分享几个不错的 Linux 运维脚本&#xff0c;这些脚本中大量使用了 Linux 的文本三剑客&#xff1a; awkgrepsed 建议大家这三个工具都要了解并最好能够较为熟练的使用 根据 PID 显示进程所有信息 根据用户输入的PID&#xff0c;过滤出该PID所有的信息 #! /b…

MySQL(三)SQL优化

SQL优化插入数据大批量数据插入主键优化order by优化group by优化limit优化count优化update优化插入数据 需要一次性往数据库表中插入多条记录&#xff0c;可以从以下三个方面进行优化 insert into tb_test values(1,tom); insert into tb_test values(2,cat); insert into t…