“最大连续子序列和”、“最大递增子序列”、“最大公共子序列”、“最长公共子串”问题总结【持续更新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

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

hdu1689(线段树成段更新)

两种操作:1、set区间[a,b]上数字为v;2、查询[ 1 , n ]上的sum 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdl

poj2406(连续重复子串)

题意:判断串s是不是str^n,求str的最大长度。 解题思路:kmp可解,后缀数组的倍增算法超时。next[i]表示在第i位匹配失败后,自动跳转到next[i],所以1到next[n]这个串 等于 n-next[n]+1到n这个串。 代码如下; #include<iostream>#include<algorithm>#include<stdio.h>#include<math.

poj3261(可重复k次的最长子串)

题意:可重复k次的最长子串 解题思路:求所有区间[x,x+k-1]中的最小值的最大值。求sa时间复杂度Nlog(N),求最值时间复杂度N*N,但实际复杂度很低。题目数据也比较水,不然估计过不了。 代码入下: #include<iostream>#include<algorithm>#include<stdio.h>#include<math.h>#include<cstring

spoj705( 求不相同的子串个数)

题意:求串s的不同子串的个数 解题思路:任何子串都是某个后缀的前缀,对n个后缀排序,求某个后缀的前缀的个数,减去height[i](第i个后缀与第i-1 个后缀有相同的height[i]个前缀)。 代码如下: #include<iostream>#include<algorithm>#include<stdio.h>#include<math.h>#include<cstrin