其他分享
首页 > 其他分享> > 最长公共子序列问题

最长公共子序列问题

作者:互联网

最长公共子序列问题

问题描述:给你两个字符串\(s\)和\(t\),找出这两个字符串的最长公共子序列

一些定义:

\(LCS\)问题的最优子结构性

定理\(6.2\) 设有序列\(X=<x_1,x_2,...,x_m>\)和\(Y=<y_1,y_2,...,y_n>\),并设序列\(Z=<z_1,z_2,...,z_k>\)为\(X\)和\(Y\)的任意一个\(LCS\)。

证明:

上述定义说明两个序列的一个\(LCS\)也包含了两个序列的前缀的\(LCS\),也即\(LCS\)问题具有最优子结构性质。

问题一

1143. 最长公共子序列

思路:定义状态\(f_{i , j}\)表示前缀序列\(s_i\)和\(t_j\)的一个\(LCS\)的长度,则根据上述分析有转移方程

\[f_{i , j} = \begin{cases} 0 & i = 0 \;or\;j = 0\\ f_{i - 1 , j - 1 } + 1 & i > 0 , j > 0 , s_i = t_j\\ max(f_{i , j - 1} , f_{i- 1 , j }) & i > 0 , j >0 , s_i \neq t_j \end{cases} \]

时间复杂度:\(O(|s| \times |t|)\)

参考代码:

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int n = text1.size(),m = text2.size();
        text1 = '0'+text1;text2 ='1'+text2;
        vector<vector<int>>f(n+1,vector<int>(m+1,0));
        for(int i = 1 ; i <= n ; ++i)
            for(int j = 1 ; j <= m ; ++j){
                f[i][j] = max(f[i-1][j],f[i][j-1]);
                if(text1[i] == text2[j]) f[i][j] = max(f[i][j],f[i-1][j-1]+1);
            }
        return f[n][m];
    }
};

如果要输出\(LCS\),可以尝试做做这题AT4527 LCS

问题二

P1439 【模板】最长公共子序列

题目描述:给出 \(1,2,...,n\) 的两个排列\(P_1\)和\(P_2\)求它们的最长公共子序列。

数据范围:$ 1\leq n \leq 10^5$

思路:如果直接使用问题一种的状态转移方程,则复杂度为\(O(n^2)\),显然不可接受。考虑到此题的两个序列是两个排列,也即序列中的元素各不相同,我们令\(A = P_1\) , \(B= P_2\)如果我们定义映射关系\(g(B_i) = i\),然后再让\(A_i = g(A_i)\) , 那么\(B\)序列就变成了\(1 , 2 , ..., n\) , 而\(A\)就是\(1 , 2 , ... , n\)的一个排列。此时要求这两个序列的最长公共子序列等价于求解序列\(A\)的一个最长上升子序列\(LIS\)。事实上,只要\(A,B\)两个序列中有一个的元素是各不相同的就可以使用这种方法对原问题进行转化然后求解。

时间复杂度:\(O(nlogn)\)

参考代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;

int a[N], b[N], c[N];
int n;
int LIS() {
	int len = 1;
	c[len] = b[1];
	for (int i = 2; i <= n; ++i) {
		if (b[i] > c[len]) c[++len] = b[i];
		else {
			int l = 1, r = len, pos = 0;
			while (l <= r) {
				int mid = l + r >> 1;
				if (c[mid] < b[i]) pos = mid, l = mid + 1;
				else r = mid - 1;
			}
			c[pos + 1] = b[i];
		}
	}
	return len;
}
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i <= n; ++i) scanf("%d", &b[i]);
	for (int i = 1; i <= n; ++i) c[a[i]] = i, a[i] = i;
	for (int i = 1; i <= n; ++i) b[i] = c[b[i]];
	printf("%d\n", LIS());
	return 0;
}

问题三

P2758 编辑距离
72. 编辑距离

题目描述:设\(A\)和\(B\)是两个字符串。我们要用最少的字符操作次数,将字符串\(A\)转换为字符串\(B\)。这里所说的字符操作共有三种:1. 插入一个字符;2.删除一个字符;3.替换一个字符。(均为小写字母。)

思路:定义状态\(f_{i , j}\)表示串\(A\)长度为\(i\)的前缀与串\(B\)长度为\(j\)的前缀的编辑距离。当\(A_i = B_j\)时,\(f_{i , j} = f_{i - 1 , j - 1}\),否则,有以下三种方案:

故其转移方程为:

\[f_{i , j} = \begin{cases} 0 & i = 0 , j = 0\\ f_{i - 1 , j - 1} & A_i = B_j\\ min\{f_{i - 1 , j - 1} , f_{i - 1 , j} , f_{i , j - 1}\} + 1 & A_i \neq B_j \end{cases} \]

时间复杂度:\(O(|A| \times |B|)\)
参考代码:

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
#define rrep(i, n) for (int i = 0; i <= (n); ++i)
using std::cin;
using std::cout;
using std::string;
using std::vector;
using std::min;

int main() {
	string A, B;
	cin >> A >> B;
	
	int n = A.size(), m = B.size();
	A = ' ' + A, B = ' ' + B;
	vector<vector<int>> f(n + 1, vector<int>(m + 1, 1e8));
	
	rrep(i, n) f[i][0] = i;
	rrep(i, m) f[0][i] = i;
	
	rep(i, n)rep(j, m) {
		f[i][j] = min(f[i - 1][j], f[i][j - 1]) + 1;
		int t = A[i] != B[j];
		f[i][j] = min(f[i][j], f[i - 1][j - 1] + t);
	}
	
	cout << f[n][m] << '\n';
	
	return 0;
}

标签:前缀,int,公共,序列,长度,最长,LCS
来源: https://www.cnblogs.com/cherish-/p/15729993.html