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

相关文章

linux生产者,消费者问题

pthread_cond_wait() :用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。 pthread_cond_wait() 必须与pthread_mutex 配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread

关于C++中的虚拟继承的一些总结(虚拟继承,覆盖,派生,隐藏)

1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下: class A class B1:public virtual A; class B2:pu

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

2024.6.24 IDEA中文乱码问题(服务器 控制台 TOMcat)实测已解决

1.问题产生原因: 1.文件编码不一致:如果文件的编码方式与IDEA设置的编码方式不一致,就会产生乱码。确保文件和IDEA使用相同的编码,通常是UTF-8。2.IDEA设置问题:检查IDEA的全局编码设置和项目编码设置是否正确。3.终端或控制台编码问题:如果你在终端或控制台看到乱码,可能是终端的编码设置问题。确保终端使用的是支持你的文件的编码方式。 2.解决方案: 1.File -> S

公共筛选组件(二次封装antd)支持代码提示

如果项目是基于antd组件库为基础搭建,可使用此公共筛选组件 使用到的库 npm i antdnpm i lodash-esnpm i @types/lodash-es -D /components/CommonSearch index.tsx import React from 'react';import { Button, Card, Form } from 'antd'

vcpkg安装opencv中的特殊问题记录(无法找到opencv_corexd.dll)

我是按照网上的vcpkg安装opencv方法进行的(比如这篇:从0开始在visual studio上安装opencv(超详细,针对小白)),但是中间出现了一些别人没有遇到的问题,虽然原因没有找到,但是本人给出一些暂时的解决办法: 问题1: 我在安装库命令行使用的是 .\vcpkg.exe install opencv 我的电脑是x64,vcpkg在这条命令后默认下载的也是opencv2:x6

十五.各设计模式总结与对比

1.各设计模式总结与对比 1.1.课程目标 1、 简要分析GoF 23种设计模式和设计原则,做整体认知。 2、 剖析Spirng的编程思想,启发思维,为之后深入学习Spring做铺垫。 3、 了解各设计模式之间的关联,解决设计模式混淆的问题。 1.2.内容定位 1、 掌握设计模式的"道" ,而不只是"术" 2、 道可道非常道,滴水石穿非一日之功,做好长期修炼的准备。 3、 不要为了

问题-windows-VPN不正确关闭导致网页打不开

为什么会发生这类事情呢? 主要原因是关机之前vpn没有关掉导致的。 至于为什么没关掉vpn会导致网页打不开,我猜测是因为vpn建立的链接没被更改。 正确关掉vpn的时候,会把ip链接断掉,如果你不正确关掉,ip链接没有断掉,此时你vpn又是没启动的,没有域名解析,所以就打不开网站。 你可以在打不开网页的时候,把vpn打开,你会发现网络又可以登录了。 方法一 注意:方法一虽然方便,但是可能会有

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测 目录 时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测基本介绍程序设计参考资料 基本介绍 MATLAB实现LSTM时间序列未来多步预测-递归预测。LSTM是一种含有LSTM区块(blocks)或其他的一种类神经网络,文献或其他资料中LSTM区块可能被描述成智能网络单元,因为

人工智能机器学习算法总结神经网络算法(前向及反向传播)

1.定义,意义和优缺点 定义: 神经网络算法是一种模仿人类大脑神经元之间连接方式的机器学习算法。通过多层神经元的组合和激活函数的非线性转换,神经网络能够学习数据的特征和模式,实现对复杂数据的建模和预测。(我们可以借助人类的神经元模型来更好的帮助我们理解该算法的本质,不过这里需要说明的是,虽然名字是神经网络,并且结构等等也是借鉴了神经网络,但其原型以及算法本质上还和生物层面的神经网络运行原理存在