这里总结一下字符串类型的动态规划问题,一般涉及最值问题,方案数问题都可以使用动态规划来解决

最短编辑距离

这个问题显然是一个最优解问题,对于最优解问题我们可以考虑动态规划解题,因此我们得先考虑子问题,既从a[1...i]到b[1..j]的最短编辑距离。

我们设状态变量:

        f [i][j] = 表示从a[1...i]到b[1...j]的编辑距离

        那么我们每一次的修改,删除,添加都会使编辑距离增长,同时也会影响的字符的状态,那么我们就要选择一个最优的操作这样才能保证最后的结果最优,那么如何选择操作呢?

        当a[i] = b[j] 时,既当两个字符匹配相同的时候,说明我们这步不需要任何操作,那么我们只要从上一次状态既f[i-1][j-1]这个操作次数转移过来即可

        当a[i] != b[j]时,既当两个字符不相等,那么我们就应该从修改,插入,删除里面选一个最优状态来作为当前状态,那我们如何用状态方程来表示修改,插入,删除呢?

        我们以 L O V T ----> L O V E 为例

修改操作:

                        比如,当a[i](T) != b[j](E) 的时候,我们知道的是LOV其实已经拼凑出来了,也就是说我们要拿到拼出LOV的编辑次数再加上将T--->E的修改操作次数就等于最后一步为修改操作时的编辑距离,用状态方程既为 f[i][j] = f[i-1][j-1]+1

删除操作 :

                如果是通过删除操作得到的LOVE,那也就是说我们前一个状态LOV已经将LOVE拼凑出来了,现在只要将T删除就得到了LOVE,用状态方程可以表示为:f[i][j] = f[i-1][j]+1

插入操作:

        如果最后一步操作为插入操作,也就是我们只要将LOVT--->LOV的操作次数再加一步插入E即可得到LOVE,用状态方程可以表示为:f[i][j] = f[i][j-1]+1

    因此我们将状态方程分为两大类:

        当 a[i] == b[j] 时,f[i][j] = f[i-1][j-1]

        当a[i] !=b[j]时:

                               修改:f[i][j] = f[i-1][j-1]+1

                               删除:f[i][j] = f[i-1][j]+1

                               插入:f[i][j] = f[i][j-1]+1

         然后在这三个状态里面选取一个最小值作为当前的最优状态

public class Main {public static void main(String[] args) {Scanner sr = new Scanner(System.in);String s1 = sr.next();String s2 = sr.next();int n1 = s1.length();int n2 = s2.length();String[] t1 = s1.split("");//将字符转化为字符数组String[] t2 = s2.split("");int f[][] = new int[n1+1][n2+1];//开设状态方程数组 f[i][j] 代表将前i个字符编辑为前j个字符for(int i=1;i<=n2;++i) f[0][i] = i;//边界条件初始化for(int i=1;i<=n1;++i)f[i][0] = i;for(int i=1;i<=n1;++i) {//从第一个字符开始遍历for(int j=1;j<=n2;++j) {if(t1[i-1].equals(t2[j-1])) {//比较两个字符是否相等f[i][j] = f[i-1][j-1];//如果相等直接从上一个字符转移过来}else {f[i][j]=Math.min(f[i-1][j]+1, Math.min(f[i][j-1]+1, f[i-1][j-1]+1));//如果不相等则从修改删除插入中选一个最优解}}}System.out.print(f[n1][n2]);//打印出最后结果}
}

从状态方程我们可以发现,第i个状态只与i-1的状态相关,因此我们可以通过滚动数组优化空间复杂度,将空间复杂度从mn降为n

但要注意的是边界条件的初始化要有所调整,其他情况都是一样的

public class Main {public static void main(String[] args) {Scanner sr = new Scanner(System.in);String s1 = sr.next();String s2 = sr.next();int n1 = s1.length();int n2 = s2.length();String[] t1 = s1.split("");//将字符转化为字符数组String[] t2 = s2.split("");int f[][] = new int[2][n2+1];//开设状态方程数组 f[i][j] 代表将前i个字符编辑为前j个字符for(int i=1;i<=n2;++i) f[0][i] = i;//边界条件初始化for(int i=1;i<=n1;++i) {//从第一个字符开始遍历for(int j=0;j<=n2;++j) {int now = i%2;//通过将i%2进行滚动if(j==0) {f[now][j] = f[1-now][j]+1;continue;} if(t1[i-1].equals(t2[j-1])) {//比较两个字符是否相等f[now][j] = f[1-now][j-1];//如果相等直接从上一个字符转移过来}else {f[now][j]=Math.min(f[1-now][j]+1, Math.min(f[now][j-1]+1, f[1-now][j-1]+1));//如果不相等则从修改删除插入中选一个最优解}}}System.out.print(f[n1%2][n2]);//打印出最后结果}
}

最长公共子序列

由于是求最长公共子串,那么我们可以通过先求出较短串的最长公共子串,然后自底向上求出原问题的最长公共子串,比如要求解abccd---aecd的长度,那么我们可以先求出abc--aec的最长公共子串进而推到abccd---aecd的公共子串的长度,也就是说这题是可以用动态规划来求解的,因此我们得写出状态转移方程,我们用f[i][j]表示a[1..i]到b[1..j]的最长公共子串,此时存在两种情况:

        1、当a[i] == b[j]时:

        也就是此时字符匹配上了,那么就应该增加公共子串的长度,将前一个字符的最长公共子串的长度+1,既f[i][j] = f[i-1][j-1]+1

        2、当a[i] != b[j]时:

          此时字符并没有匹配上,我们应该把前一个字符匹配的结果直接转移过来就行了,但这里存在两种情况:

                既当a[i]在公共子串中时:f[i][j]=f[i][j-1]

                当b[j]在公共字串中:f[i][j]=f[i-1][j]

因此我们应该在这两种情况里面取一个最大值作为当前状态的最优值:

                f[i][j]=Math.max(f[i][j-1],f[i-1][j]) 

所以状态方程应为:

             当a[i] != b[j] :f[i][j] = f[i-1][j-1]+1

             当a[i] != b[j]时:  f[i][j]=Math.max(f[i][j-1],f[i-1][j]) 

由于我们每一次的状态都由上一步的状态转移过来,因此我们可以通过滚动数组来优化代码,将时间复杂度由O(MN)优化为O(N)

import java.util.Scanner;public class 最长公共子序列 {/** 思路:*       定义两个字符串,我们要分别比较两字符是否相等:*          1、如果相等则从上一个字符转移并且加1*            2、如果不相等那么直接从上一个字符转移 */public static void main(String[] args) {Scanner sr = new Scanner(System.in);String s1 = sr.next();String s2 = sr.next();int m1 = s1.length();int m2 = s2.length();String[] t1 = s1.split("");String[] t2 = s2.split("");int [][] f = new int[2][m2+1];//定义状态方程 f[i][j] 代表a[1..i]到b[1..j]的最长公共子序列for(int i=1;i<=m1;++i) {int now = i%2;//定义滚动数组for(int j=1;j<=m2;++j) {if(t1[i-1].equals(t2[j-1])) {//第i个字符和第j个字符相等f[now][j] = f[1-now][j-1]+1;}else {//不相等时两种情况取最大值f[now][j] = Math.max(f[1-now][j],f[now][j-1]);}}}System.out.print(f[m1%2][m2]);}
}

数组切分--JavaB G

我们这题求的是一段数组能被切分的方案数,假设我们求解的是前i个数的方案数,这里就要分为两种情况来看:

        1、如果我们把第i个字符看成单独一份,那么我们就要将前i-1个数的方案数转移过来

        2、如果我们将第i个字符与前i-1,i-2,i-3看成一段,那么我们就要判断[i..i-1,i-2,i-3]这个区间是否连续,如果连续的话,说明这一段是可以单独看成一段,那么我们就应该把这种情况加上,这样我们就可以往前一直枚举并判断枚举的[i...i-t..1]这段是否连续,如果连续的话就将该情况加上,否则就pass

        因此我们就可以通过动态规划来解决这个问题,我们假设f[i]为第i位数切分的方案数,假设原问题数组长度为m,那么我们可以通过从f[1]一直递推到f[m],至底向上求解问题

状态方程:

        f[i] = 第i位数切分数组的方案总数

        但这里要注意一个问题,我们怎么才能判断[i..i-t]这段是否连续呢,假设我们现在要求解前i个数的方案数,当我们判断[i..i-1]这段是否连续时,我们只需要判断这段的max-min是否等于整个区间的长度-1如果等于则代表这段是连续的,比如4 5 6 这段的max=6,min=4 则6-4 = 3-1说明这段是连续的,因此我们在往前枚举的时候得记录这个区间的最大值和最小值,这样我们才能判断这个区间是否是连续的。

public class Main {public static void main(String[] args) {Scanner sr = new Scanner(System.in);int N = sr.nextInt();int mod = 1000000007;//对结果求模,防止结果过大int [] num = new int[N+1];int [] dp  = new int[N+1];dp[0] = 1;for(int i=1;i<=N;++i) {num[i] = sr.nextInt();}//1 3 2 4  for(int i=1;i<=N;++i) {//往前递推自底向上求解boolean flag = true;dp[i] =(dp[i]+dp[i-1])%mod;//将第i个字符单独看成一段,此时就将i-1的方案数直接转移过来int max = num[i];int min = num[i];for(int j=i-1;j>=1;--j) {if(num[j]>max)max=num[j];if(num[j]<min)min=num[j];if(i-j==max-min){//表示这一段连续,那么就将[i..i-t]这段当成一段直接加上dp[i] =(dp[i]+dp[j-1])%mod;}}}System.out.println(dp[N]);//输出结果,表示第N位长度能切分数组的方案数}
}

子串---NOIP2015

子串(原题)

这题其实跟上面的数组切分思考维度是一致的,这题不同的点在于将一个字符串提取k个字符与另一个字符串相匹配的方案数,我们假设在a[1..i]中选k段与b[1..j]匹配,那么我们有两种可能:

        1、如果a[i] != b[j] 说明最后一个字符不匹配,那么这个字符肯定不会在匹配的字符中出现,我们就应该从上一个字符i-1(a[1..i-1])到j b[1...j]且匹配k次的方案转移过来。

        2、如果a[i]==b[j]说明最后一个字符是匹配的,我们可以将最后一个字符单独看成一段,那么我们只要从a[1..i-1]选出k-1段与b[1...j-1]相匹配就ok了,但是可能不止一个字符是匹配上的,比如 i-2与j-2匹配,i-t与j-t匹配,那么最坏的可能是匹配的字符长度为n,那么这里就得再枚举一遍n,因此我们必须考虑优化,这里我们可以单独拿一个数组res[i][j][k](代表前i个字符取出k个字符与前j个字符匹配并且第i个字符与第j个字符相等)来记录尾部字符匹配的方案数,当a[i] == b[j]时res[i][j][k]=上一个字符取k-1次与第j-1个字符匹配的方案数+res[i-1][j-1][k],如果a[i] != b[j],直接将res记为0

import java.util.Scanner;
/** 思路:*        s1 字符和 s2字符进行匹配*            当每往前枚举一个s1字符的时候都要对s2前面的字符进行匹配,因为要找出的是与b相等的字串,那么如果新更新的字符与b字符的尾部不相等的话*             那么当前的状态一定是由上一个状态转移过来的,还有一种情况是如果相等,那么当前状态应该是前一个字符匹配k次相等的状态+上几个字符匹配*             相等的状态量之和,现在问题就是如何表示上几个字符匹配相等的状态量之和,这里我们不妨开辟一个数组res,记录前几个字符相等情况下状态之和*            通过以上分析 我们不难发现下一层的状态只与上一层的状态有关系,那么此时可以使用滚动数组进行更新*        res :*          i,j,k 表示前i个字符,匹配到j段,选取p次的次数,该数组由两部分更新来:*                1、f[i-1][j-1][k-1] 此时匹配到的字符单独算一段,所以前面应该是p-1段,所以当前的方案数应该是f [i-1][j-1][k-1]*              2、  */
public class Main {public static void main(String[] args) {Scanner sr = new Scanner(System.in);int m = sr.nextInt();//A 字符串的长度 abcadbint n = sr.nextInt();//B 字符串的长度 abint p = sr.nextInt();//从A 中取出p个互不重叠的字符串String s1 = sr.next();//s1字符串String s2 = sr.next();//s2字符串      int [][][] f = new int[2][n+1][p];//定义一个状态数组,表示前m个字符匹配到了前n个字符,取出p次的状态量int [][][] res = new int[2][n+1][p];//定义一个状态数组,记录m个字符匹配到了前n个字符,取出p次中存在相等字符的情况之和for(int i=1;i<=m;++i) {//第一层枚举m次int now = i%2;//利用now进行滚动更新for(int j=1;j<=n;++j) {//第二层枚举n次for(int k =1;k<=p;++k) {//第三层枚举kif(s1.charAt(i-1)!=s1.charAt(j-1))res[i][j][k] = 0;//不匹配的时候直接把res赋值为0else {res[i][j][k] = res[i-1][j-1][k]+f[i-1][j-1][k-1];//匹配到的时候,之前(i-1,j,k)的方案数+前m个字符匹配相等的方案数的总和}       f[i][j][k] = f[i-1][j][k]+res[i][j][k];}}}System.out.print(f[m][n][p]);}
}

【动态规划】字符串类型动态规划相关推荐

  1. 【算法】动态规划 ④ ( 动态规划分类 | 坐标型动态规划 | 前缀划分型动态规划 | 前缀匹配型动态规划 | 区间型动态规划 | 背包型动态规划 )

    文章目录 一.动态规划场景 二.动态规划分类 1.坐标型动态规划 2.前缀划分型动态规划 3.前缀匹配型动态规划 4.区间型动态规划 5.背包型动态规划 一.动态规划场景 动态规划 动态规划使用场景 ...

  2. 动态规划常见类型总结

    本文针对动态规划的常见类型进行总结.虽说总结的是动态规划,但顺便把递推也放了进来.严格来说,递推不属于动态规划问题,因为动态规划不仅有递推过程,还要有决策(即取最优),但广义的动态规划是可以包含递推的 ...

  3. [动态规划|字符串] leetcode 5 最长回文子串

    [动态规划|字符串] leetcode 5 最长回文子串 1.题目 题目链接 给定一个字符串 s,找到 s 中最长的回文子串.你可以假设 s 的最大长度为 1000. 示例1: 输入: "b ...

  4. 字符串类型的算法面试

    字符串类型的算法面试题特点 1. 广泛性 1.字符串可以看做字符类型的数组与数组排序.查找.调整有关 2.很多其它类型的面试题可以看做字符串类型的面试题 注意:用JAVA实现字符串类型的题目是,由于j ...

  5. 动态规划-03-线性动态规划

    一.简介 动态规划主要任务,确定状态方程(fn 和 fn-1.fn-2),和边界条件(n=1,2),联想高中递推问题. 线性动态规划的主要特点是状态的推导是按照问题规模 i 从小到大依次推过去的,较大 ...

  6. 【动态规划】线性动态规划

    吐槽:动态规划这个东西,只要推不出状态转移方程,一切都白搭 基础知识 一. 动态规划 动态规划中最重要的三个概念:最优子结构,重复子问题,无后效性. 最优子结构:如果问题的最优解所包含的子问题的解也是 ...

  7. MySQL数据类型--------字符串类型实战

    1. 背景 * MySQL支持的字符串类型有CHAR.VARCHAR.二进制.var二进制.BLOB.文本等等. 2. 字符串类型所需的存储和值范围 类型 说明 N的含义 是否有字符集 最大长度 CH ...

  8. Python字符串类型及操作总结

    1.字符串表示 两种类型四种表示 单行-一对单引号或一对双引号 "python" 'python' 多行-一对三单引号或一对三双引号 '''python''' "&quo ...

  9. R将字符串类型(Character)转化为因子类型(Factor)

    R将字符串类型(Character)转化为因子类型(Factor) 目录 R将字符串类型(Character)转化为因子类型(Factor)

最新文章

  1. php mysql 编程原理_PHP开发的原理及优势介绍
  2. poj 1192(简单树形dp)
  3. hdu 4679 树的直径
  4. junit 测试 dao_JUnit测试Spring Service和DAO(带有内存数据库)
  5. html 手机访问优化,移动端首屏优化
  6. phpcmsV9留言插件提交后返回上一页实现方法
  7. android 长时间 build,Gradle(Android Studio)构build时间非常长
  8. pycharm快捷键之①“上下移动某一行“②参数提示
  9. 浅谈临床研究中随机化
  10. 解决win10小娜Cortana(win+S)无法搜索本地应用或无反应
  11. 开灯问题 算法竞赛 (注释详细)
  12. Python 数据处理工具 Pandas(上)
  13. win7用计算机名无法访问局域网,Win7系统在局域网内无法访问文件内容的解决方法...
  14. 斌终于说出小米9缺货内幕,取消开售后,雷军对备货量充满自信
  15. SQLyog 64位破解版 v12.09
  16. Eclipse怎样改变字体大小
  17. Codeforces 439 A. Devu, the Singer and Churu, the Joker
  18. Codeforces Round #619 (Div. 2)
  19. MATLAB画正方体
  20. STM32F051——USART

热门文章

  1. 如何用Python爬取网易云歌曲?秘诀在这~
  2. mybatis-plus生成java代码
  3. 直接添加集合对象Arrays.asList()
  4. springboot-No7 加入异常拦截机制ExceptionHandler
  5. Win7 注册ocx控件 “DllRegisterServer的调用失败,错误代码为0x80040200 ”
  6. num2str(matlab自定义函数)
  7. ChatGPT技术原理 第七章:Seq2Seq模型
  8. LeetCode C++基础面试题汇总附答案(一)
  9. HI3516EV100 AEC回音消除 + ANR语音降噪功能实现
  10. 如何设置Java环境变量