LCS/LIS/LCIS

本文详细介绍了LCS(最长公共子序列)和LCIS(最长公共上升子序列)的算法思想。对于LCS,通过动态规划实现O(n^2)的时间复杂度,并解释了状态转移方程。而对于LCIS,同样采用动态规划,讨论了如何在O(n^2)时间内找到最长公共上升子序列,并给出了相应的算法和代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LCS

这里仅仅记录一下LCS算法的思路:典型的DP,空间换时间

step 1: 先比较两个子串的对应位是否相同,如果相同,则b[i][j]='\',并且c[i][j]=c[i-1][j-1]+1,否则转向step2

step 2:如果上角的值>=左角的值,那么将上角的值赋给c[i][j],同时b[i][j]箭头指向大值(指向上角)。如果上角的值<左角的值,箭头指向大值(指向左),左角的值赋给c[i][j]


实例:

求X=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>的最长公共子序列,在第一行中,第一行为A和第4列第6列的值A相同,

于是b[][]中存储的箭头指向上对角。同时c[][]的值为上对角的值+1。如果X,Y对应的相应位不同,那么b[][]的箭头总是

指向大值,如果两值相等,则指向上面那一个。遍历完之后,就可以得出b[][]和c[][],就能确定LCS。

要点:

1.b[i][j]存放的是箭头,若Xi==Yi,则箭头指向对角线b[i-1][j-1],否则箭头总是指向大值,

若上值b[i-1][j]和左值b[i][j-1]相等,优先指向上值。
2.c[i][j]存放的是数值,若Xi==Yi,则c[i][j]值为上对角值c[i-1][j-1]+1,否则c[i][j]=max(c[i][j-1],c[i-1][j])

代码:

#include<iostream>
using namespace std;
#define M 7
#define N 6
int b[M+1][N+1]={0};//保存箭头
int c[M+1][N+1]={0};//保存值

void Lcs_Length(char* X,char* Y)
{
	int i,j;
	for(i=1;i<=M;i++)//第0列置0
		c[i][0]=0;
	for(j=0;j<=N;j++)
		c[0][j]=0;  //第0行置0
	for(i=1;i<=M;i++)
	{
		for(j=1;j<=N;j++)
		{
			if(X[i]==Y[j])//比较两个字串对应位,如果相等,则=对角+1
			{
				c[i][j]=c[i-1][j-1]+1;
				b[i][j]=1;//1代表↖ 
			}
			else if(c[i-1][j]>=c[i][j-1])//如果上面值>=下面值
			{
				c[i][j]=c[i-1][j];//c[i][j]=大值
				b[i][j]=2;//2代表↑
			}
			else 
			{
				c[i][j]=c[i][j-1];//赋的总是大值,箭头总是指向大值
				b[i][j]=3;//3代表←
			}
		}
	}
}
void Print_Lcs(char* X,int i,int j)
{
	if(i==0 || j==0)
		return ;
	if(b[i][j]==1)
	{
		Print_Lcs(X,i-1,j-1);
		cout<<X[i]<<' ';//只需输出↖对应的值
	}
	else if(b[i][j]==2)
		Print_Lcs(X,i-1,j);
	else Print_Lcs(X,i,j-1);
}
void main()
{
	 char  X[M+1] = {'0','A','B','C','B','D','A','B'};  
	 char  Y[N+1] = {'0','B','D','C','A','B','A'};  
    Lcs_Length(X, Y);  
    Print_Lcs(X, M, N);  
	cout<<endl;
	for(int i=0;i<=M;i++)
	{
		for(int j=0;j<=N;j++)
		{
			cout<<c[i][j]<<' ';
		}
		cout<<endl;
	}
	cout<<endl;
	for(int i=0;i<=M;i++)
	{
		for(int j=0;j<=N;j++)
		{
			switch(b[i][j])
			{
			case 0:
				{
					cout<<b[i][j]<<"  ";
					break;
				}
			case 1:
				{
					cout<<"↖"<<' ';
					break;
				}
			case 2:
				{
					cout<<"↑"<<' ';
					break;
				}
			case 3:
				{
					cout<<"←"<<' ';
					break;
				}
			}
		}
		cout<<endl;
	}
}

LCIS

以下来自百度文库:

最长公共上升子序列(LCIS)的O(n^2)算法

预备知识:动态规划的基本思想,LCS,LIS。

问题:字符串a,字符串b,求a和b的LCIS(最长公共上升子序列)。

首先我们可以看到,这个问题具有相当多的重叠子问题。于是我们想到用DP搞。DP的首要任务是什么?定义状态。

1定义状态F[i][j]表示以a串的前i个字符b串的前j个字符且以b[j]为结尾构成的LCIS的长度。

为什么是这个而不是其他的状态定义?最重要的原因是我只会这个,还有一个原因是我知道这个定义能搞到平方的算

法。而我这只会这个的原因是,这个状态定义实在是太好用了。这一点我后面再说。

<think>嗯,用户问的是C++的最长公共上升子序列。首先,我得确认他们需要的是这个问题的解决方法,还是想了解概念。最长公共上升子序列(LCIS)结合了最长公共子序列(LCS)和最长上升子序列(LIS)的特点,所以可能需要先解释一下这个概念。 然后,我得回忆一下LCIS的动态规划解法。通常,这类问题会用一个二维数组dp[i][j]来表示处理到第一个数组的第i个元素和第二个数组的第j个元素时的状态。但可能需要优化空间复杂度,或者有没有更高效的方法? 比如,可能有一个优化的方法,遍历第二个数组的同时维护一些状态,比如当前的最大长度,或者某种条件下的最大值。这样可以将时间复杂度降到O(n^2)而不是更高的复杂度。比如,原版的LCS是O(n^2),而LIS如果使用优化方法可以到O(n log n),但这里两者结合可能需要不同的处理。 举个例子,假设有两个数组a和b。我们需要找到既是两者的公共子序列,又是严格递增的。这个时候,动态规划的状态设计可能需要考虑到当前元素的值,以确保递增的条件。 可能的状态转移方程是怎样的?比如,当a[i] == b[j]的时候,我们需要找到一个k,使得b[j] > 当前某个值,然后更新dp[j]的值。或者,可以维护一个数组,其中dp[j]表示以b[j]结尾的LCIS的长度。这样的话,每次处理a中的元素时,遍历b数组,找到符合条件的j的位置,并更新dp值。 比如,当处理到a的第i个元素时,对于每个b中的元素j,如果a[i] == b[j],那么我们需要找到在b中比b[j]小的所有位置k,然后dp[j] = max(dp[j], dp[k]+1)。或者,可以维护一个变量来记录当前的最大值,从而避免重复遍历。 或者,另一种方法是,对于每一个元素在数组a中,遍历数组b中的元素,同时记录当前的最大长度。比如,对于数组b中的每个元素,如果它等于当前a中的元素,并且之前的元素满足递增条件,则更新对应的dp值。 这个时候可能需要一个双重循环,外层遍历数组a的元素,内层遍历数组b的元素,并维护一个变量来记录在当前位置之前的最大可行长度。这样时间复杂度是O(nm),其中n和m是两个数组的长度。 比如,初始化一个数组dp,长度为m,初始为0。然后对于每个a中的元素a[i],维护一个变量current_max,初始为0。然后遍历b数组中的每个元素b[j],如果a[i] > b[j],则current_max = max(current_max, dp[j]),因为这样当后面遇到a[i]等于某个b[j]时,可以继承这个current_max加1。如果a[i]等于b[j],则dp[j] = current_max + 1,这样保证递增的条件。 这样处理的话,当a[i]等于b[j]时,dp[j]的值取决于之前所有比b[j]小的元素的dp值中的最大值加1,这样确保了上升的特性。同时,因为a和b的公共元素必须按顺序出现,所以这样处理可以保证公共子序列的顺序。 现在需要将这个过程用C++代码实现的话,可能需要两个数组,或者优化空间使用一个一维数组。例如,用一个一维数组dp,其中dp[j]表示以b[j]结尾的最长公共上升子序列的长度。 具体的代码结构可能是这样的: 初始化dp数组为0。 遍历数组a中的每个元素x: 初始化current_max为0。 遍历数组b中的每个元素y及其索引j: 如果y < x,则current_max = max(current_max, dp[j]) 否则如果y == x,则dp[j] = max(dp[j], current_max + 1) 最后,dp数组中的最大值就是LCIS的长度。 这样处理的话,时间复杂度是O(nm),空间复杂度是O(m),如果数组b的长度是m的话。 然后需要考虑边界情况,比如两个数组中有重复元素,或者其中一个数组为空的情况。另外,是否需要严格递增还是非严格递增?题目中的“上升”通常指严格递增,所以在判断条件时要注意。 例如,如果a数组是[1,3,2,4],b数组是[1,2,3,4],那么LCIS应该是[1,2,4]或者[1,3,4],长度3。 测试这样的例子是否能被正确处理。 现在,在代码实现时,需要注意循环的顺序。外层是a的每个元素,内层是遍历b数组。每次处理a中的一个元素时,先遍历b数组,记录比当前a元素小的位置的最大dp值,然后在遇到相等的元素时更新对应的dp[j]。 这样,每次处理a的元素时,可以正确累积前面的信息。例如,当a[i]等于b[j]时,此时current_max保存的是所有在b中位置j之前且元素值小于a[i]的位置的最大dp值,因此将dp[j]更新为current_max+1是正确的。 最后,遍历dp数组找到最大值即可。 这样的算法应该能正确处理问题。那现在需要给出一个C++的示例代码。例如: #include <iostream> #include <vector> #include <algorithm> using namespace std; int LCIS(vector<int>& a, vector<int>& b) { int m = b.size(); vector<int> dp(m, 0); for (int x : a) { int current_max = 0; for (int j = 0; j < m; ++j) { if (b[j] < x) { current_max = max(current_max, dp[j]); } else if (b[j] == x) { dp[j] = max(dp[j], current_max + 1); } } } return *max_element(dp.begin(), dp.end()); } int main() { vector<int> a = {1, 3, 2, 4}; vector<int> b = {1, 2, 3, 4}; cout << LCIS(a, b) << endl; // 输出3 return 0; } 这样应该可以得到正确的结果。当然,需要处理其他情况,比如数组为空的情况。但在这个示例中,假设输入的数组都是非空的。 总结来说,回答用户的问题时,应该先解释LCIS的定义,然后介绍动态规划的解法,说明状态转移的思路,再给出代码示例,并解释其原理。同时需要注意时间复杂度和空间复杂度,以及可能的优化点。最长</think>公共上升子序列(Longest Common Increasing Subsequence,LCIS)是结合了最长公共子序列(LCS)和最长上升子序列(LIS)的问题,要求找到两个序列中既是公共子序列又是严格递增的最长子序列。以下是详细解释和C++实现: --- ### **问题描述** 给定两个整数序列 `a` 和 `b`,找到它们的最长公共上升子序列的长度。 --- ### **动态规划解法思路** 1. **状态定义**: 使用一维数组 `dp[j]`,表示以 `b[j]` 结尾的最长公共上升子序列的长度。 2. **状态转移**: - 遍历 `a` 的每个元素 `x`,同时在遍历 `b` 时维护一个变量 `current_max`。 - 当 `b[j] < x` 时,更新 `current_max` 为当前已知的最大可能长度。 - 当 `b[j] == x` 时,将 `dp[j]` 更新为 `current_max + 1`,确保子序列严格递增。 3. **时间复杂度**:O(n*m),其中 `n` 和 `m` 是两序列的长度。 --- ### **C++代码实现** ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int LCIS(const vector<int>& a, const vector<int>& b) { int m = b.size(); vector<int> dp(m, 0); // dp[j] 表示以 b[j] 结尾的LCIS长度 for (int x : a) { int current_max = 0; // 维护当前x之前的最大可行长度 for (int j = 0; j < m; ++j) { if (b[j] < x) { current_max = max(current_max, dp[j]); // 更新current_max } else if (b[j] == x) { dp[j] = max(dp[j], current_max + 1); // 更新以b[j]结尾的LCIS } } } return *max_element(dp.begin(), dp.end()); // 返回dp数组中的最大值 } int main() { vector<int> a = {1, 3, 2, 4}; vector<int> b = {1, 2, 3, 4}; cout << LCIS(a, b) << endl; // 输出3(序列为1,2,4 或 1,3,4) return 0; } ``` --- ### **关键点解释** - **双循环遍历**:外层遍历 `a` 的元素,内层遍历 `b`,确保公共子序列的顺序。 - **current_max 的作用**:记录当前 `a` 元素之前所有小于 `b[j]` 的元素能构成的最大长度,保证严格递增。 - **空间优化**:使用一维数组而非二维数组,空间复杂度降至 O(m)。 --- ### **示例分析** 对于输入 `a = [1,3,2,4]` 和 `b = [1,2,3,4]`: - 公共上升子序列包括 `[1,2,4]` 和 `[1,3,4]`,长度均为3。 - 代码通过动态规划正确计算得到结果。 该算法高效且直观,适用于中等规模的输入。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Raise

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值