动态规划——子序列问题

news2024/12/26 22:55:51

文章目录

目录

文章目录

前言

一、动态规划思路简介

二、具体实现

1. 字符串问题

1.1 最长公共字符串

1.2.0 最长回文子串

1.2.1 最长回文子序列

2.编辑距离问题

2.1 判断子序列

2.2 编辑距离

总结


前言

上文提到动态规划的背包问题,本文继续介绍动态规划的另一种应用:子序列问题。子序列分为间断子序列与连续子序列,其中诸多问题例如最大递增子序列、最长回文串等等,都可以用动态规划的思路解决


一、动态规划思路简介

动态规划解题的关键有两步。

第一步是找出题目中量的递推关系式。动态规划的代码实现是依据递推关系展开的,找出递推关系是最关键,也是最先要处理的一件事情。

第二步是依据递推关系明确dp数组的下标含义,并进行正确的初始化。

接下来我们从一些具体问题看一下如何使用动态规划思路解决问题。

二、具体实现

1. 字符串问题

1.1 最长公共字符串

请编写一个 C 语言程序来查找两个字符串的最长公共子串(由字符串中连续的一段字符组成的字符串)

输入两个字符串str1和str2, 字符串长度范围均为[0, 200)。

输出两个字符串的最长公共子串,如果不存在公共子串,则输出“没有公共子串”;如果有多个最长公共子串,则输出str1中从左至右第一个最长公共子串。

示例输入

abbbcc
accd

示例输出

cc

解题思路:先寻找有没有递推关系式。观察发现,此类最长问题,一般有 “当前位”的最长公共字符串长度等于“前一位”的最长公共字符串长度加一。

由递推关系可知,遍历数组的方向应该从前往后遍历,并有dp[i]=dp[i-1]+1,此处i表示在字符串中的位置,dp[i]表示以第i位为结尾的公共字符串长度。

具体代码

#include<stdio.h>
#include<string.h>
int main(){
	char a[300]={0},b[300]={0};
	scanf("%s%s",a+1,b+1);
//	dp[i][j]表示a的i位,b的j位相同的情况下 从前向后数公共字串的长度
	int dp[300][300]={0};
//初始化为0的原因在于默认字符不相等,在遍历时检测是否相等,若相等则加一
	int alen=strlen(a+1),blen=strlen(b+1);
	int maxlen=0,maxLocation=1;
	for(int i=1;i<=alen;i++){
		for(int j=1;j<=blen;j++){
			if(a[i]==b[j]){
				dp[i][j]=dp[i-1][j-1]+1;
				if(maxlen<dp[i][j]){
					maxlen=dp[i][j];
					maxLocation=i-maxlen+1;//找起始位置 
				}
			}
		}
	}
	for(int i=0;i<maxlen;i++){
		printf("%c",a[maxLocation+i]);
	}
	return 0;
}

1.2.0 最长回文子串

5. 最长回文子串 - 力扣(LeetCode)

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

示例 2:

输入:s = "cbbd"
输出:"bb"

解题思路:观察回文串规律,发现回文串有如下特征:若回文串字符数为奇数,则以第i位为中心向左右检索,如果左右边缘的字符相等,则回文串字符数加二。若回文串字符为偶数,则以第i位与第 i+1 位为中心向左右检索。

设dp1[i]与dp2[i],前者表示在第i位向前数有多少个回文字符,dp2同理。

代码样例

char* longestPalindrome(char* s){
    int dp1[1000]={0},dp2[1000]={0};
	int len=strlen(s);
    for(int i=0;i<len;i++){
        //dp1[i]表示对i加上中心后一半的长度
        while(i-dp1[i]>=0&&i+dp1[i]<len){
        	if(s[i-dp1[i]]!=s[i+dp1[i]])
            	break;
            dp1[i]++;
		}
        if(s[i]==s[i+1]&&i+1<len){
            while(i-dp2[i]>=0&&i+1+dp2[i]<len){
            	if(s[i-dp2[i]]!=s[i+1+dp2[i]])
            		break;
				dp2[i]++;
			}
        }
    }
    int max=2*dp1[1]-1,maxlocation=0;
    for(int i=0;i<len;i++){
    	dp1[i]=2*dp1[i]-1;
    	dp2[i]=2*dp2[i];
        if(max<(dp1[i]>dp2[i]?dp1[i]:dp2[i])){
        	max=dp1[i]>dp2[i]?dp1[i]:dp2[i];
        	maxlocation=i+1-(max+1)/2;
		}
	}
    char*a=(char*)malloc(1010);
	strncpy(a,s+maxlocation,max);
    a[max]=0; 
    return a;
}

1.2.1 最长回文子序列

516. 最长回文子序列 - 力扣(LeetCode)

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

示例 1:

输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。

示例 2:

输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。

解题思路:回文数组规律一般可由中间向两边推知。故知,对于 i 到 j 的子序列而言,若s [ i ] = s [ j ] 。其最大回文子序列数必定是dp[ i +1] [ j -1 ]+2。若s [ i ]不等于s [ j ],那么考虑加入左侧元素或右侧元素后能否使子序列数增加,即比较放入左侧元素与放入右侧元素后的最大子序列数。

if(s[left]==s[right]){
                dp[left][right]=dp[left+1][right-1]+2;
            }else{
                int max=dp[left+1][right]>dp[left][right-1]?dp[left+1][right]:dp[left][right-1];
                dp[left][right]=dp[left+1][right-1];
            }

在动态规划解题过程中,我们只考虑按遍历顺序下的当前步未知,前面已遍历过的dp应该已知,并不再考虑它们是怎么计算得出的。因此对遍历顺序有较强的要求。

对于dp[ i ][ j ],i 表示左边界,j 表示右边界,dp[ i ][ j ]表示最大序列数。因为计算dp[ i ][ j ]需要

dp[ i+1][ j ]   dp[ i+1 ][ j-1]   dp[ i ][ j-1],所以遍历顺序应该i递减,j递增,从而保持完备性。

示例代码:

int longestPalindromeSubseq(char* s) {
    int dp[1010][1010]={0};
    //表示从i到j的最长回文序列
    int len=strlen(s);
    for(int i=0;i<len;i++)
        dp[i][i]=1;
    for(int left=len-1;left>=0;left--){
        for(int right=left+1;right<len;right++){
            if(s[left]==s[right]){
                dp[left][right]=dp[left+1][right-1]+2;
            }else{
                int max=dp[left+1][right]>dp[left][right-1]?dp[left+1][right]:dp[left][right-1];
                dp[left][right]=max;
            }
        }
    }
    return dp[0][len-1];
}

2.编辑距离问题

2.1 判断子序列

392. 判断子序列 - 力扣(LeetCode)

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace""abcde"的一个子序列,而"aec"不是)。

示例1

输入:s = "abc", t = "ahbgdc"
输出:true

示例2

输入:s = "axc", t = "ahbgdc"
输出:false

解题思路f[i][j] 存储了从位置 i 开始,字符 j + 'a' 在字符串 t 中的下一个出现位置。首先初始化 f 数组并填充,之后遍历 s 中的每个字符,在 f 数组中找到对应字符的下一个出现位置并更新。如果在任何时刻无法找到字符,返回 false,否则返回 true

具体代码

bool isSubsequence(char* s, char* t) {
    int n = strlen(s), m = strlen(t);

    int f[m + 1][26];
    memset(f, 0, sizeof(f));
    for (int i = 0; i < 26; i++) {
        f[m][i] = m;
    }

    for (int i = m - 1; i >= 0; i--) {
        for (int j = 0; j < 26; j++) {
            if (t[i] == j + 'a')
                f[i][j] = i;
            else
                f[i][j] = f[i + 1][j];
        }
    }
    int add = 0;
    for (int i = 0; i < n; i++) {
        if (f[add][s[i] - 'a'] == m) {
            return false;
        }
        add = f[add][s[i] - 'a'] + 1;
    }
    return true;
}

2.2 编辑距离

72. 编辑距离 - 力扣(LeetCode)

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数  。

你可以对一个单词进行如下三种操作:

  • 插入一个字符

  • 删除一个字符

  • 替换一个字符

示例1

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

示例2 

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

解题思路

定义一个 dp 数组,dp[i][j] 表示将 word1[0..i-1] 转换为 word2[0..j-1] 的最小编辑距离。然后初始化 dp 数组的第一行和第一列,分别代表从空字符串到其他字符串的编辑距离。

对每一对字符 (word1[i-1], word2[j-1]),如果相等,dp[i][j] = dp[i-1][j-1],否则考虑三种操作(删除、插入、替换),选择最小值。

最终结果在 dp[n][m],其中 nm 分别是 word1word2 的长度。

具体代码

int minDistance(char* word1, char* word2) {
    int n = strlen(word1), m = strlen(word2);
    int dp[n+1][m+1];
    // 初始化边界条件
    for (int i = 0; i <= n; i++) dp[i][0] = i;
    for (int j = 0; j <= m; j++) dp[0][j] = j;
    // 填充 dp 数组
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            int cost = (word1[i-1] == word2[j-1]) ? 0 : 1;
            dp[i][j] = fmin(fmin(dp[i-1][j] + 1, dp[i][j-1] + 1), dp[i-1][j-1] + cost);
        }
    }
    return dp[n][m];
}


总结

动态规划在子序列问题中应用广泛,关键是找出递推关系并设计合适的 dp 数组。通过精妙的状态转移,可以高效解决如最长公共子串、回文子序列及编辑距离等问题。

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

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

相关文章

蓝桥杯准备训练(lesson1,c++方向)

前言 报名参加了蓝桥杯&#xff08;c&#xff09;方向的宝子们&#xff0c;今天我将与大家一起努力参赛&#xff0c;后序会与大家分享我的学习情况&#xff0c;我将从最基础的内容开始学习&#xff0c;带大家打好基础&#xff0c;在每节课后都会有练习题&#xff0c;刚开始的练…

【开源】A059-基于SpringBoot的社区养老服务系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看项目链接获取⬇️&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600个选题ex…

winform跨线程更新界面

前言&#xff1a; 大家好&#xff0c;我是上位机马工&#xff0c;硕士毕业4年年入40万&#xff0c;目前在一家自动化公司担任软件经理&#xff0c;从事C#上位机软件开发8年以上&#xff01;我们在开发C#程序的时候&#xff0c;有时候需要在非Ui主线程更新界面&#xff0c;为了…

无界(wujie)微前端项目搭建,nginx线上部署,pnpm一键安装依赖、启动应用,git代码仓库存放方式

这里写自定义目录标题 1. 创建项目项目目录布局选择主应用子应用 2. pnpm包管理&#xff0c;一键安装、启动、打包pnpm一键安装依赖npm-run-all 一键启动、打包 3. nginx线上部署主应用中子应用中nginx文件目录及配置 git代码存放方式 1. 创建项目 主应用&#xff1a; vue3vit…

10.容器-list列表

定义一个list使用[] 定义一个空列表 [] 或者 list() 列表中每个元素之间用逗号隔开 a_list [aa, bb, cc] print(a_list) # <class list> print(type(a_list)) list列表可以存储不同类型的元素 a_list [aa, bb, cc] print(a_list) # <class list> print(type…

BiGRU:双向门控循环单元在序列处理中的深度探索

一、引言 在当今的人工智能领域&#xff0c;序列数据的处理是一个极为重要的任务&#xff0c;涵盖了自然语言处理、语音识别、时间序列分析等多个关键领域。循环神经网络&#xff08;RNN&#xff09;及其衍生结构在处理序列数据方面发挥了重要作用。然而&#xff0c;传统的 RN…

PDF与PDF/A的区别及如何使用Python实现它们之间的相互转换

目录 概述 PDF/A 是什么&#xff1f;与 PDF 有何不同&#xff1f; 用于实现 PDF 与 PDF/A 相互转换的 Python 库 Python 实现 PDF 转 PDF/A 将 PDF 转换为 PDF/A-1a 将 PDF 转换为 PDF/A-1b 将 PDF 转换为 PDF/A-2a 将 PDF 转换为 PDF/A-2b 将 PDF 转换为 PDF/A-3a 将…

计费结算系统的架构设计思路

背景 近期负责关于集团的计费结算相关的系统&#xff0c;相对于2C系统的大流量&#xff0c;高并发的场景&#xff0c;计费和结算的信息对稳定性要求更高。对时效性要求并没有过于严苛的要求。那么接下来就和大家分享一下计费结算系统的架构设计。 模块划分 我们暂且将平台细分…

人工智障(5)

今天kimi把我气疯了&#xff0c;你们看原对话&#xff1a; 月之暗面最近在搞什么&#xff0c;不仅算力慢&#xff0c;而且回答离谱的要死&#xff0c;难道换老板了&#xff1f;

Python爬虫——城市数据分析与市场潜能计算(Pandas库)

使用Python进行城市市场潜能分析 简介 本教程将指导您如何使用Python和Pandas库来处理城市数据&#xff0c;包括GDP、面积和城市间距离。我们将计算每个城市的市场潜能&#xff0c;这有助于了解各城市的经济影响力。 步骤 1: 准备环境 确保您的环境中安装了Python和以下库&…

Python毕业设计选题:基于Flask的医疗预约与诊断系统

开发语言&#xff1a;Python框架&#xff1a;flaskPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 系统首页 疾病信息 就诊信息 个人中心 管理员登录界面 管理员功能界面 用户界面 医生…

Android 图形系统之二:ViewRootImpl

ViewRootImpl简介 ViewRootImpl 是 Android UI 系统的核心类之一&#xff0c;负责将 View 层级树与窗口管理器 WindowManager 联系起来。它是Android 应用视图的根节点&#xff0c;与 WindowManager 结合&#xff0c;实现视图的绘制、事件分发、窗口更新等功能。虽然 ViewRoot…

python通过ODBC连接神通数据库

1、安装神通数据库 2、安装python 3、安装pyodbc pip3 install pyodbc-5.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl 注&#xff1a;pyodbc要和python版本相对应 4、安装unixodbc 5、配置神通数据库ODBC数据源 6、示例代码如下 #!/usr/bin/python…

基于单片机的智能药箱设计

本设计主要由红外检测传感器、显示、独立按键、舵机、语音以及短信等模块组成。红外传感器模块主要对药仓中的药物数据进行采集&#xff0c;采集完毕由主控制器进行数据加工&#xff0c;之后可传送至显示模块上进行显示&#xff0c;在显示模块也可对显示时间、吃药倒计时、吃药…

【掩体计划——DFS+缩点】

题目 代码 #include <bits/stdc.h> using namespace std; const int N 1e5 10; vector<vector<int>> g; bool st[N]; int ans 1e9; bool dfs(int f, int u, int dis) {bool is 1;for (auto j : g[u]){if (j f)continue;is & dfs(u, j, dis (g[u].…

无人机点云处理算法技术解析!

一、核心技术 数据预处理&#xff1a; 数据预处理是点云处理的第一步&#xff0c;主要包括滤波、去噪、数据压缩等。滤波技术可以去除点云数据中的噪声和孤立点&#xff0c;提高数据质量。常用的滤波方法包括双边滤波、高斯滤波等。 数据压缩则用于减少数据量&#xff0c;提…

Android13 允许桌面自动旋转

一&#xff09;需求-场景 Android13 实现允许桌面自动旋转 Android13 版本开始后&#xff0c;支持屏幕自动旋转&#xff0c;优化体验和兼容性&#xff0c;适配不同屏幕 主界面可自动旋转 二&#xff09;参考资料 android framework13-launcher3【06手机旋转问题】 Launcher默…

vue+uniapp+echarts的使用(H5环境下echarts)

1.安装 npm install echarts4.9.0 --save // 带版本号 2.main.js中全局引用 // import echarts from echarts // 如果是5.0以上版本用这个 import * as echarts from echarts Vue.prototype.$echartsecharts 3.使用 <template><view id"box" style"w…

探索仓颉编程语言:官网上线,在线体验与版本下载全面启航

文章目录 每日一句正能量前言什么是仓颉编程语言仓颉编程语言的来历如何使用仓颉编程语言在线版本版本下载后记 每日一句正能量 当你被孤独感驱使着去寻找远离孤独的方法时&#xff0c;会处于一种非常可怕的状态。因为无法和自己相处的人也很难和别人相处&#xff0c;无法和别人…

【Elasticsearch】Docker安装和基本概念

1. Docker安装ES 拉取es镜像 docker pull elasticsearch:8.5.3 创建网络 docker network create oj-network 启动es docker run -d --name oj-es-dev -e "ES_JAVA_OPTS-Xms256m -Xmx256m" -e "discovery.typesingle-node" -v D:\javacode\oj-byte\depl…