本篇主要介绍最长公共子序列问题和最大子段和问题

1、最长公共子序列问题

什么是最长公共子序列

给定一个序列X=<x1,x2,x3,x4…,xm>,另一个序列Z=<z1,z2,z3,z4…,zk>,若存在一个严格递增的X的下标序列<i1,i2,i3,…,ik>对所有的1,2,3,…,k,都满足x(ik)=zk,则称Z是X的子序列

比如说:比如Z=<B,C,D,B>是X=<A,B,C,B,D,A,B>的子序列

如果Z既是X的子序列,又是Y的子序列,则称Z为X和Y的公共子序列

比如说
X:A B C B D A B
Y:B D C A B A

形如这样的就是最长公共子序列问题

那我们接下来用动态规划算法解最长公共子序列问题

用动态规划算法解最长公共子序列问题


我们可以把x1,x2,x3,…,xi和y1,y2,y3,…,yi共同组合一个子问题

然后我们看一下子问题间的依赖关系
假设大长方形框是一个原问题,左边是x序列,上边是y序列
若xm=yn,我们可以把原问题归结成xm-1和yn-1组合的子问题。

若xm!=yn,我们可以把原问题归结成xm-1和yn组合的子问题

或者归结成xm和yn-1组合的子问题

我们看到啊,在xi和yi上,如果能找到一个最长的公共子序列,xi+1和yi+1就可以在它的基础上继续寻找。对于原问题而言,子问题的最优解是包含在了原问题当中。因此这种算法是满足依赖关系。

接下来我们来看下递推方程
令C[i,j]:xi与yi的公共子序列长度
如果xi=yi,那么C[i,j]=C[i-1,j-1]+1
如果x1!=y1,那么我们就在C[i,j-1]和C[i-1,j]中找到最大值赋给C[i,j]
当然若i=0或j=0,C[i,j]=0。因为此时有一个序列没有长度,所以就没有公共子序列

下面来看一下追踪解:
首先我们先考虑标记函数
设B[i,j]是一个标记函数
我们需要从c[0][0]开始填表,填到c[m-1][n-1],所得到的c[m-1][n-1]就是LCS的长度
在对应字符相等的时候,用↖标记

在p1 < p2的时候,用←标记

在p1 >= p2的时候,用↑标记

我们看一个例子:
比如说求ABCBDAB和BDCABA的最长公共子序列长度:

我们分析下。
首先C[0,j]和C[i,0]=0
xi和yj进行比较,如果xi=yj。那么C[i,j]=C[i-1,j-1]+1,我们看一下C[1,4]=C[0,3]+1,然后标记斜向上的箭头
x1和y1进行比较,因为C[i-1,j]>=C[i,j-1],则让C[i,j]=C[i-1,j],然后标记向上的箭头
x1和y5进行比较,因为C[i,j-1]>C[i-1,j],则让C[i,j]=C[i,j-1]=1,此时标记向左的箭头
循环往复
i=0,i<字符串1的长度,i++
j=0,j<字符串2的长度,j++
在这个循环遵照上边的规则把表填出来就可以了

这个标记函数的指向就是怎么推出来的这个值

对于追踪解的过程,就是从最后一个值自底向上开始追踪,按照箭头的走向开始求解。如果遇到斜向上的箭头,那就说明找到了一个公共子序列。一直到0为止,图中画圆圈的就是问题的解即:BCBA。当然我们也要知道,这个解不是唯一的,我们能看到BCAB也是一个解。所以我们只需要找到一个最长子序列即可。

对于求解过程啊,因为原问题是要划成子问题来解的。那么原问题是需要依赖子问题的解,也就是说通过子问题的解和递推方程可以计算出原问题的解。所以解的箭头指向就是指向上一个子问题的最优解。因此这个方法是有效的。

通过分析,我们直接上代码:

public static void main(String[] args) {// TODO Auto-generated method stubString str1="ABCBDAB";String str2="BDCABA";int B[][]=new int[str1.length()+1][str2.length()+1];int temp[][]=LcsLength(str1, str2, B);System.out.println("最长公共子序列的个数是:"+temp[str1.length()][str2.length()]);System.out.print("最长公共子序列是:");Tracingsolution(str1.length(), str2.length(), B, str1);}public static int[][] LcsLength(String str1,String str2,int B[][]) {int [][]C=new int[str1.length()+1][str2.length()+1];for(int i=0;i<str1.length();i++) {for(int j=0;j<str2.length();j++) {if(str1.charAt(i)==str2.charAt(j)) {C[i+1][j+1]=C[i][j]+1;B[i+1][j+1]=0;  //0表示斜向上}else { if(C[i][j+1]>=C[i+1][j]) {C[i+1][j+1]=C[i][j+1];  //将最大的值存放在C中B[i+1][j+1]=1;  //1表示竖直方向}else {C[i+1][j+1]=C[i+1][j];B[i+1][j+1]=-1;  //-1表示向左}}}}return C;}//递归追踪解public static void Tracingsolution(int a,int b,int B[][],String str1) {if(a==0||b==0) return;//箭头斜向上if(B[a][b]==0) {Tracingsolution(a-1, b-1, B, str1);System.out.print(str1.charAt(a-1));}//箭头向上else if(B[a][b]==1)Tracingsolution(a-1, b, B, str1);//箭头向左elseTracingsolution(a, b-1, B, str1);}

下面,我们来进行时间复杂度分析:

动态规划算法来解最长公共子序列问题分析

我们看到动态规划算法里面是两层for循环的
那么时间复杂度就是O(mn)
追踪解的代码是逐步缩小的过程,要么是m-1,要么是n-1或者是m-1和n-1同时执行。那么在最坏情况下的时间复杂度就是O(m+n)。最后我们能得出时间复杂度就是O(mn)

而空间复杂度是一个备忘录的表格。所以是O(mn)

这就是动态规划解最长公共子序列问题的内容

2、最大子段和问题

什么是最大子段和问题

给定n个数(可以是负数)的序列(a1,a2,…,an)
求在这个序列中,某一连续段中的最大和

例如:(-2,11,-4,13,-5,-2)
则最大子段和就是a2+a3+a4=20

我们可以考虑用蛮力法来解

蛮力法解最大子段和问题

我们可以求这个序列的子集(连续段),然后对这个子集进行求和。直至找到最大值
譬如说:a1,a1+a2,a1+a2+a3,a2,a3…

我们对蛮力法进行代码分析:
用锁定范围来求解
让i=0,i<n;i++
j=i,j<n,j++
我们令k在i和j之间游走。即,k=i,k<=j,k++
遍寻max(sum+a[k])的值。如果我们找到了最大值,我们就用m,n记录下。m=i,n=j。然后
最大值,m,n就是问题的解
好,我们来上代码:

    public static int m;  //记最大子段和的初始位置public static int n;  //记最大子段和的末尾位置public static void main(String[] args) {// TODO Auto-generated method stubint a[]=new int[] {-2,11,-4,13,-5,-2};int maxsum=MaxsubsegmentsMax(a);Tracingsolution(a, maxsum);System.out.print("最大子段和是:");for(int i=m;i<=n;i++){if(i!=n)System.out.print("a"+i+"+");else System.out.print("a"+i);}System.out.print("="+maxsum);}//蛮力法解最大子段和public static int MaxsubsegmentsMax(int a[]) {int maxsum=-0x3f3f3f3f,tempsum=0;for(int i=0;i<a.length;i++)  //子序列初始位置for(int j=i;j<a.length;j++) {  //子序列末尾位置tempsum=0;for(int k=i;k<=j;k++) tempsum+=a[k];if(tempsum>maxsum){maxsum=tempsum;m=i+1;n=j+1;}}return maxsum;}
蛮力法解最大子段和问题的分析

我们看到,这是有三层循环的,所以蛮力法的时间复杂度就是O(n^3)

当然我们也可以用分治法来解最大子段和问题

分治法解最大子段和问题

我们可以在中间点将序列一分为二,通过递归将左右序列求解。对于中间部分。一定记住是从中间往两遍算。因为是要以中间为基准向两遍计算。从而得到跨边界的最大值。不然就会变成零零散散的区域,再合成就不是中间跨边界的值了。最后将左半部分的和,右半部分的和,跨边界的和,三者进行比较找到最大的和。

对于解的追踪,我们是要分开考虑的,跨边界的问题中记下最大和的时候左右的下标,而在左半部分和右半部分中,left和right的跨度就是最大和的左右下标。
然后我们要在三者之间进行比较。将对应的情况赋给m和n。

具体代码如下:

public static int m;  //记最大子段和的初始位置public static int n;  //记最大子段和的末尾位置public static int max=-0x3f3f3f3f;  //递归法求解最大值public static void main(String[] args) {// TODO Auto-generated method stubint a[]=new int[] {-2,11,-4,13,-5,-2};int maxsum=MaxsubsegmentsMax2(a, 0, a.length-1);Tracingsolution(a, maxsum);System.out.print("最大子段和是:");for(int i=m;i<=n;i++){if(i!=n)System.out.print("a"+i+"+");else System.out.print("a"+i);}System.out.print("="+maxsum);}//递归解最大子段和public static int MaxsubsegmentsMax2(int a[],int left,int right) {int x = 0,y = 0;if(left==right)return a[left];int mid=(left+right)>>1;int leftMaxSum=MaxsubsegmentsMax2(a,left,mid);  //划成左半部部分int rightMaxSum=MaxsubsegmentsMax2(a, mid+1, right);  //划成右半部部分//处理跨边界问题int leftmax=-0x3f3f3f3f,rightmax=-0x3f3f3f3f,tempmax=0;//计算中间向左部分的最大值//一定记住是从中间往两遍算。因为是要以中间为基准向两遍计算。从而得到跨边界的最大值。不然就会变成零零散散的区域,再合成就不是中间跨边界的值了for(int i=mid;i>=left;i--) {tempmax=a[i]+tempmax;if(tempmax>leftmax) {leftmax=tempmax;x=i;}}tempmax=0;//计算中间向右部分的最大值for(int i=mid+1;i<=right;i++) {tempmax=a[i]+tempmax;if(tempmax>rightmax) {rightmax=tempmax;y=i;}}//将跨边界的左右值加起来int midMaxSum=leftmax+rightmax;//将左半部分,中间边界,右半部分中挑选一个最大值if(leftMaxSum>Math.max(midMaxSum, rightMaxSum)) {max=leftMaxSum;m=left+1;n=right+1;}else if (midMaxSum > Math.max(leftMaxSum, rightMaxSum)) {max = midMaxSum;m=x+1;n=y+1;}else {max = rightMaxSum;m=left+1;n=right+1;}return max;}

下面我们来分析时间复杂度

分治法解最大子段和问题的分析

我们想一下,从中间往左算最大和的时候,是不是A[k],A[k]+A[k-1],A[k]+A[k-1]+A[k-2],…,A[k]+A[k-1]+A[k-2]+…+A[1]
因此我们就能分析出,时间复杂度就是左半部分的时间复杂度就是O(n),同理右半部分的时间复杂度也是O(n)。所以左右一加还是O(n)
因此我们列出递推方程:
T(n)=2T(n/2)+O(n)
根据主定理得出
T(n)=O(nlogn)
分析得出分治法比蛮力法降到了O(nlogn)量级的时间复杂度

下面考虑动态规划解最大子段和问题

动态规划解最大子段和问题



我们这么分析,动态规划算法可以进行试探算法。
我们可以i=0,i<n;i++
tempsum+a[i]如果大于此时的a[i]。那我们就加,否则就tempsum=a[i]。一轮循环之后让tempsum和sum比较。

这个是将前i个最优解和第i个值进行比较。如果C[i-1]+A[i]大,就进行计算,否则就是A[i]的值。这样总是能获得一个最大的C[i]。然后再用最优解去和下一个子问题进行计算。因此是满足依赖关系的。

至于追踪解,我们通过比较是只能获取最大和的那个末位置的边界c。要自底向上的去求初位置的边界。怎么求呢?
将计算好的最大值max,以此减去A[c],得到的新max然后判断是否为0且max!=A[- -c]。如果是这样就结束循环,这个c就是初位置下标。否则继续计算直到条件满足。最后就得到初位置下标

好,下面直接上代码

    public static int m;  //记最大子段和的初始位置public static int n;  //记最大子段和的末尾位置public static void main(String[] args) {// TODO Auto-generated method stubint a[]=new int[] {-2,11,-4,13,-5,-2};int maxsum=MaxsubsegmentsMax3(a);Tracingsolution(a, maxsum);System.out.print("最大子段和是:");for(int i=m;i<=n;i++){if(i!=n)System.out.print("a"+i+"+");else System.out.print("a"+i);}System.out.print("="+maxsum);}//用动态规划解最大子段和public static int MaxsubsegmentsMax3(int a[]) {int tempsum=0,maxsum=-0x3f3f3f3f;for(int i=0;i<a.length;i++) {if(tempsum+a[i]>a[i])tempsum=tempsum+a[i];elsetempsum=a[i];if(tempsum>maxsum) {maxsum=tempsum;n=i+1;}}return maxsum;}//追踪解public static void Tracingsolution(int a[],int maxsum) {int k=n-1;while(maxsum!=0||maxsum==a[k]) {maxsum=maxsum-a[k];k--;}m=k+2;}

下面我们进行时间复杂度分析

动态规划算法来解最大子段和问题分析

我们看到动态规划算法里面只有一层循环,因此是O(n)
而对于追踪解,最坏时间复杂度也是O(n)
所以动态规划算法来解最大子段和问题的时间复杂度就是O(n)

而空间复杂度我们自始至终就只用一个数组,因此空间复杂度就是O(n)

以上就是最大子段和问题的内容

######            物情无巨细,自适固其常。 (杜甫《夏夜叹》)

动态规划算法下的序列问题:最长公共子序列问题和最大子段和问题相关推荐

  1. 【算法设计与分析】最长公共子序列问题 动态规划算法 超详细

    最长公共子序列问题描述 注意:最长公共子序列不一定是连续序列. 例如:"ASAFAGAHAJAK"与"AAAAAAA"的最长公共子序列为:AAAAAA 公共子序 ...

  2. python【数据结构与算法】动态规划详解从背包到最长公共子序列(看不懂你来打我)

    文章目录 1 引入 2 01背包 3 完全背包问题 4 多重背包问题 5 最长公共子序列 1 引入 0-1背包问题是最广为人知的动态规划问题之一,拥有很多变形.尽管在理解之后并不难写出程序,但初学者往 ...

  3. 夕拾算法进阶篇:15)最长公共子序列(动态规划DP)

    题目描述 给你一个序列X和另一个序列Z,当Z中的所有元素都在X中存在,并且在X中的下标顺序是严格递增的,那么就把Z叫做X的子序列. 例如:Z=<a,b,f,c>是序列X=<a,b,c ...

  4. 数据结构与算法专题——第三题 最长公共子序列

    一:作用 最长公共子序列的问题常用于解决字符串的相似度,是一个非常实用的算法,作为码农,此算法是我们的必备基本功. 二:概念 举个例子,cnblogs这个字符串中子序列有多少个呢?很显然有27个,比如 ...

  5. 程序员的算法课(6)-最长公共子序列(LCS)

    上一节我们讲了动态规划,我们也知道,动态规划对于子问题重叠的情况特别有效,因为它将子问题的解保存在存储空间中,当需要某个子问题的解时,直接取值即可,从而避免重复计算! 这一节我们来解决一个问题,就是最 ...

  6. 单调递增最长子序列 - 从最长公共子序列到单调递增最长子序列

    最长公共子序列 的 算法思路 在这里 点击进入  将 代码稍微改动一下 就可以   ,   最长公共子序列  是两个 字符串求 公共子序列  , 可以将其中的 一个 改为 从 a 到 z  这样输入另 ...

  7. [csu/coj 1078]多个序列的最长公共子序列

    题意:给n个序列,同一个序列里面元素互不相同,求它们的最长公共子序列. 思路:任取一个序列,对于这个序列里面的两个数ai,aj(i<j),如果对于其它每一个序列,都出现过ai,aj,且ai在aj ...

  8. 算法设计-递归法解最长公共子序列问题 C代码

    给大家推荐一个公众号:诗葵1931 里面的诗歌很美 主要功能:递归法解最长公共子序列问题 #include<stdio.h> #include<string.h> /* 递归思 ...

  9. 动态规划算法解最长公共子序列LCS问题

    动态规划算法解LCS问题 作者 July 二零一零年十二月三十一日 本文参考:微软面试100题系列V0.1版第19.56题.算法导论.维基百科. 第一部分.什么是动态规划算法 ok,咱们先来了解下什么 ...

最新文章

  1. MPB:白酒酒醅非破坏性连续采集与核酸提取
  2. 19福师计算机应用基础在线作业一答案,2019秋福师《计算机应用基础》在线作业一16(100分)...
  3. LinkServer--服务器选项
  4. Android 用adb 打印linux内核调试信息dmesg和kmsg命令
  5. JVM调优笔记:认识JVM内存模型(jdk1.8)
  6. c++ 模板教程(c语言中文网) 自己运行实例
  7. 容器可以作为全局变量吗_四季青是风水树吗?哪些可以作为风水树?
  8. 【Java面试题】49 垃圾回收的优点和原理。并考虑2种回收机制。
  9. 发布会不用开了?三星Galaxy Note 10 Plus完整规格泄露
  10. matlab 非线性辨识,非线性系统辨识Matlab实现
  11. C语言实现三子棋游戏
  12. win10系统计算机物理地址,Win10如何修改物理地址?Win10修改网卡物理地址(MAC)的两种方法...
  13. 服务器中的软件如何备份文件夹在哪里找,itunes备份文件在哪,如何找到itunes的备份文件...
  14. 屏幕录制(Giphy Capture)并导出Gif动图
  15. Android花样Text设置神器之SpanableString
  16. vs2013 打开失败 未能完成操作,不支持此接口
  17. 【EDM邮件营销】独立站卖家如何通过用户标签提高EDM邮件打开率
  18. ViewPager圆形指示器
  19. 第一个Andriod应用
  20. Java程序员怎么规划五年职业生涯

热门文章

  1. pythonxy官网下载_GitHub - Lidaguo/codeparkshare: Python初学者(零基础学习Python、Python入门)书籍、视频、资料、社区推荐...
  2. 又一个beanCopy引发的血案metaspace溢出
  3. 包装类(装箱与拆箱)
  4. 从零实现简易播放器-0.音视频基本概念
  5. QQ for linux 腾讯值得深思
  6. POP展示的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  7. 什么是Facebook像素?作用是什么?
  8. 简单快捷测试java数据库是否连接成功
  9. 根据图片连接(url),将连接转化为blod地址进行下载
  10. Vue 图片、PDF预览(Blod数据类型)