“最大连续子序列和”、“最大递增子序列”、“最大公共子序列”、“最长公共子串”问题总结【持续更新ing】

本文主要是介绍“最大连续子序列和”、“最大递增子序列”、“最大公共子序列”、“最长公共子串”问题总结【持续更新ing】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、最大连续子序列和(最大子序列)

leetcode链接:https://leetcode.com/problems/maximum-subarray/

最大子序列是要找出由数组成的一维数组中和最大的连续子序列。比如{5,-3,4,2}的最大子序列就是 {5,-3,4,2},它的和是8,达到最大;而 {5,-6,4,2}的最大子序列是{4,2},它的和是6。

解法①:暴力解法

一般情况下,先从暴力解分析,然后再进行一步步的优化。

求子序列和,那么我们要知道子序列的首尾位置,然后计算首尾之间的序列和。用2个for循环可以枚举所有子序列的首尾位置。 然后用一个for循环求解序列和。这里时间复杂度太高,O(n^3).

复杂度分析

  • 时间复杂度: O(n^3)
  • 空间复杂度: O(1)

解法②:前缀和 + 暴力解

前面解法①的暴力解法中,时间复杂度太高,其中包括了很多重复计算的操作,比如计算子序列和时,S[i, j]其实可以表示为S[0,j]-S[0, i-1]。用空间换时间,申请长度为n的数组,存储前缀和序列。

复杂度分析

  • 时间复杂度: O(n^2)
  • 空间复杂度: O(n) 

解法③:动态规划

复杂度分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

示例:

思路:只要前i项的和还没有小于0那么子序列就一直向后扩展,否则丢弃之前的子序列开始新的子序列,同时我们要记下各个子序列的和,最后找到和最大的子序列。

编程注意点:maxSum存储当前最大和;currSum存储当前序列和;curBegin存储当前序列和的起始位置


int maxSubSum(const vector<int> & arr,int &begin,int &end)
{int maxSum=0;int currSum=0;int newbegin=0;for(int i=0;i<arr.size();i++){currSum+=arr[i];if(currSum>maxSum){maxSum=currSum;begin=newbegin;end=i;}if(currSum<0){currSum=0;newbegin=i+1;}}return maxSum;
}

解法④:分治法

TODO

 

二、最大递增子序列

动态规划法:

设长度为N的数组为{a0,a1, a2, ...an-1),则假定以aj结尾的数组序列的最长递增子序列长度为L(j),则L(j)={ max(L(i))+1, i<j且a[i]<a[j] }。也就是说,我们需要遍历在j之前的所有位置i(从0到j-1),找出满足条件a[i]<a[j]的L(i),求出max(L(i))+1即为L(j)的值。最后,我们遍历所有的L(j)(从0到N-1),找出最大值即为最大递增子序列。时间复杂度为O(N^2)。

例如给定的数组为{5,6,7,1,2,8},则L(0)=1, L(1)=2, L(2)=3, L(3)=1, L(4)=2, L(5)=4。所以该数组最长递增子序列长度为4,序列为{5,6,7,8}。

int LIS(int *arr, int n)
{int *f = new int[n];f[0] = 1;for (int i = 1; i < n; i++){f[i] = 1;for (int j = 0; j < i; j++){if (arr[i]>arr[j] && f[j]>f[i]-1)   //注意因为可能f[i]是更新后的,因此需减去1比较f[i] = f[j] + 1;}}return f[n-1];
}

三、最大公共子序列(Longest Common Subsequence, LCS)

如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。请编写一个函数,输入两个字符串,求它们的最长公共子序列,并打印出最长公共子序列。

例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子序列,则输出它们的长度4,并打印任意一个子序列。

动态规划法:

考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bn-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:

(1) 如果am-1==bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;

(2) 如果am-1!=bn-1,则若zk-1!=am-1时,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;

(3) 如果am-1!=bn-1,则若zk-1!=bn-1时,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。

      这样,在找A和B的公共子序列时,如果有am-1==bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。

       求解:
       引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定输出最长公共字串时搜索的方向。
      我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] == Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。

      问题的递归式写成:

      回溯输出最长公共子序列过程:

 

       算法分析:
      由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(m + n)次就会遇到i = 0或j = 0的情况,此时开始返回。返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m + n)。

/** 
找出两个字符串的最长公共子序列的长度
** author :liuzhiwei  
** data   :2011-08-15
**/ 
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
int LCSLength(char* str1, char* str2, int **b)
{int i,j,length1,length2,len;length1 = strlen(str1);length2 = strlen(str2);//双指针的方法申请动态二维数组int **c = new int*[length1+1];      //共有length1+1行for(i = 0; i < length1+1; i++)c[i] = new int[length2+1];      //共有length2+1列for(i = 0; i < length1+1; i++)c[i][0]=0;        //第0列都初始化为0for(j = 0; j < length2+1; j++)c[0][j]=0;        //第0行都初始化为0for(i = 1; i < length1+1; i++){for(j = 1; j < length2+1; j++){if(str1[i-1]==str2[j-1])   //由于c[][]的0行0列没有使用,c[][]的第i行元素对应str1的第i-1个元素{c[i][j]=c[i-1][j-1]+1;b[i][j]=0;          //输出公共子串时的搜索方向}else if(c[i-1][j]>c[i][j-1]){c[i][j]=c[i-1][j];b[i][j]=1;}else{c[i][j]=c[i][j-1];b[i][j]=-1;}}}len=c[length1][length2];for(i = 0; i < length1+1; i++)    //释放动态申请的二维数组delete[] c[i];delete[] c;return len;
}
void PrintLCS(int **b, char *str1, int i, int j)
{if(i==0 || j==0)return ;if(b[i][j]==0){PrintLCS(b, str1, i-1, j-1);   //从后面开始递归,所以要先递归到子串的前面,然后从前往后开始输出子串printf("%c",str1[i-1]);        //c[][]的第i行元素对应str1的第i-1个元素}else if(b[i][j]==1)PrintLCS(b, str1, i-1, j);elsePrintLCS(b, str1, i, j-1);
}int main(void)
{char str1[100],str2[100];int i,length1,length2,len;printf("请输入第一个字符串:");gets(str1);printf("请输入第二个字符串:");gets(str2);length1 = strlen(str1);length2 = strlen(str2);//双指针的方法申请动态二维数组int **b = new int*[length1+1];for(i= 0; i < length1+1; i++)b[i] = new int[length2+1];len=LCSLength(str1,str2,b);printf("最长公共子序列的长度为:%d\n",len);printf("最长公共子序列为:");PrintLCS(b,str1,length1,length2);printf("\n");for(i = 0; i < length1+1; i++)    //释放动态申请的二维数组delete[] b[i];delete[] b;system("pause");return 0;
}

这里的输出打印函数这里我不是很明白==

四、最长公共子串

最长公共子串 和 最长公共子序列 是有区别的:

     X = <a, b, c, f, b, c>

     Y = <a, b, f, c, a, b>

     X和Y的 最长公共子序列 为<a, b, c, b>,长度为4

     X和Y的 最长公共子串 为 <a, b>长度为2

    其实Substring问题是Subsequence问题的特殊情况,也是要找两个递增的下标序列

    <i1, i2, ...ik> 和 <j1, j2, ..., jk>使

     xi1 == yj1

    xi2 == yj2

    ......

    xik == yjk

    与Subsequence问题不同的是,Substring问题不光要求下标序列是递增的,还要求每次

   递增的增量为1, 即两个下标序列为:

   <i, i+1, i+2, ..., i+k-1> 和 <j, j+1, j+2, ..., j+k-1>

    类比Subquence问题的动态规划解法,Substring也可以用动态规划解决

 令c[i][j]表示x[i]和y[j]为结尾的相同子串的最大长度:

 如  X = <y, e, d, f>,   Y = <y, e, k, f>,则:

   c[1][1] = 1

   c[2][2] = 2

   c[3][3] = 0

   c[4][4] = 1

不难得出递推关系:

   如果xi == yj, 则 c[i][j] = c[i-1][j-1]+1

   如果xi ! = yj,  那么c[i][j] = 0

最后求Longest Common Substring的长度等于   max{  c[i][j],  1<=i<=n, 1<=j<=m}

int LC_substring(char *str1, char *str2)
{int len1 = strlen(str1);int len2 = strlen(str2);int **c = new int*[len1 + 1];int i, j;for (i = 0; i < len1 + 1; i++)c[i] = new int[len2 + 1];for (i = 0; i < len1 + 1; i++)c[i][0] = 0;for (j = 0; j < len2 + 1; j++)c[0][j] = 0;int max = 0;    //记录最长公共子串长度int x, y;       //记录公共子串末尾分别在str1,str2的位置for (i = 1; i < len1 + 1; i++){for (j = 1; j < len2 + 1; j++){if (str1[i - 1] == str2[j - 1])c[i][j] = c[i - 1][j - 1] + 1;else c[i][j] = 0;if (c[i][j]>max){max = c[i][j];x = i;y = j;}}}for (i = 0; i < len1 + 1; i++)delete[] c[i];delete[] c;return max;
}

注意上述程序中未输出打印最大子串,可以在更新max值处标记当前最大子串末尾分别在str1与str2中的位置m,n;最后根据max,m,n将子串打印出。

 

这篇关于“最大连续子序列和”、“最大递增子序列”、“最大公共子序列”、“最长公共子串”问题总结【持续更新ing】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1077854

相关文章

mybatis和mybatis-plus设置值为null不起作用问题及解决

《mybatis和mybatis-plus设置值为null不起作用问题及解决》Mybatis-Plus的FieldStrategy主要用于控制新增、更新和查询时对空值的处理策略,通过配置不同的策略类型... 目录MyBATis-plusFieldStrategy作用FieldStrategy类型每种策略的作

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Python Jupyter Notebook导包报错问题及解决

《PythonJupyterNotebook导包报错问题及解决》在conda环境中安装包后,JupyterNotebook导入时出现ImportError,可能是由于包版本不对应或版本太高,解决方... 目录问题解决方法重新安装Jupyter NoteBook 更改Kernel总结问题在conda上安装了

pip install jupyterlab失败的原因问题及探索

《pipinstalljupyterlab失败的原因问题及探索》在学习Yolo模型时,尝试安装JupyterLab但遇到错误,错误提示缺少Rust和Cargo编译环境,因为pywinpty包需要它... 目录背景问题解决方案总结背景最近在学习Yolo模型,然后其中要下载jupyter(有点LSVmu像一个

解决jupyterLab打开后出现Config option `template_path`not recognized by `ExporterCollapsibleHeadings`问题

《解决jupyterLab打开后出现Configoption`template_path`notrecognizedby`ExporterCollapsibleHeadings`问题》在Ju... 目录jupyterLab打开后出现“templandroidate_path”相关问题这是 tensorflo

如何解决Pycharm编辑内容时有光标的问题

《如何解决Pycharm编辑内容时有光标的问题》文章介绍了如何在PyCharm中配置VimEmulator插件,包括检查插件是否已安装、下载插件以及安装IdeaVim插件的步骤... 目录Pycharm编辑内容时有光标1.如果Vim Emulator前面有对勾2.www.chinasem.cn如果tools工

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

Java多线程父线程向子线程传值问题及解决

《Java多线程父线程向子线程传值问题及解决》文章总结了5种解决父子之间数据传递困扰的解决方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDeco... 目录1 背景2 ThreadLocal+TaskDecorator3 RequestContextH

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

关于最长递增子序列问题概述

《关于最长递增子序列问题概述》本文详细介绍了最长递增子序列问题的定义及两种优化解法:贪心+二分查找和动态规划+状态压缩,贪心+二分查找时间复杂度为O(nlogn),通过维护一个有序的“尾巴”数组来高效... 一、最长递增子序列问题概述1. 问题定义给定一个整数序列,例如 nums = [10, 9, 2