最长公共子序列
最长公共子序列
1 | LCS是指这两个序列中最长的公共子序列, 子序列:不要求字符在原序列中连续, 但相对顺序必须保持一致 |
问题:
1 2 3 | 给定两个字符串X和Y, 我们需要找到它们最长公共子序列 X = "ABCBDAB" Y = "BDCAB" 输出最长公共子序列的长度为4 及 "BDAB" |
动态规划的思路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | dp[i][j] 表示字符串X的前i个字符和字符串Y的前j个字符组成的最长公共子序列的长度 根据X[i - 1] 和 Y[j - 1]的大小不同可以得到以下的两种情况 if x[i - 1] == y[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 else : (1) dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) # 当前字符不同, LCS的长度是移除其中一个字符后的较大值 问题(1): 为什么忽略了dp[i - 1][j - 1] 的情况, 是因为当X[i - 1] != Y[j - 1]的情况 它们不可能同时组成最长公共子序列, dp[i - 1][j - 1]是记录了前i - 1个字符和前j - 1个字符组成的最长公共子序列的长度, 由于最后两个字符不同, 导致不能使用这个状态继续递推, 于是从dp[i - 1][j] 和 dp[i][j - 1]中选择较大值, 因为 这两者包括了当前字符的最优状态 作为dp[i][j]的值 简要: dp[i - 1][j] 和 dp[i][j - 1]始终比dp[i - 1][j - 1]要多一个字符 自身的可能性 永远大于等于dp[i - 1][j - 1], 所以 只需要在dp[i - 1][j] 和 dp[i][j - 1]中选择较大值 |
那么在已经了解完后直接看题
1143. 最长公共子序列
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Solution { public : int longestCommonSubsequence(string s, string t) { int n = s.size(), m = t.size(); vector <vector < int >> dp(n + 1, vector < int >(m + 1, 0)); for ( int i = 1; i <= n; i++) { for ( int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); } } } return dp[n][m]; } }; |
降至一维
1 2 3 4 5 6 7 8 9 | 通过观察dp状态 if X[i] == Y[j]: dp[i][j] = dp[i - 1][j - 1] + 1 else : dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) 它的dp状态是 (上) (左) (左上) 如果要将它变为一维, 也就是 去处理(左上) 因为在计算当前行的时候的状态时, 左时覆盖了左上 那么也意味着去计算左上的状态时, 利用一个变量去储存当前的值 这样就可以降至一维 |
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Solution { public : int longestCommonSubsequence(string s, string t) { int n = s.size(), m = t.size(); vector < int > dp(m + 1, 0); for ( int i = 1; i <= n; i++) { int pre = dp[0]; for ( int j = 1; j <= m; j++) { int tmp = dp[j]; if (s[i - 1] == t[j - 1]) { dp[j] = pre + 1; } else { dp[j] = max(dp[j], dp[j - 1]); } pre = tmp; } } return dp[m]; } }; |
扩展:
扩展1:题目: (n <= 1e5) 和 (序列元素不出现重复)
P1439 【模板】最长公共子序列
如何转换请看如下 (根据下面这一句话):
1 2 3 4 5 6 7 8 9 10 11 12 13 | 最长公共子序列是指在两个序列中,按照顺序能找到的最大长度的相同子序列。重点是顺序要一致 在此题中 每个数字不重复, 也就意味着一一对应 比如P1[3, 2, 1, 4, 5] 和 P2[1, 2, 3, 4, 5] 可以从P1中找到对应的元素, 同时也可以在P2的相同顺序中找到, 这个部分 就是它们的公共子序列 那么可以通过映射P1, P2将P2的元素转化为位置索引, 在从P1直接找到对应P2的元素位置 这个问题就直接等价于转变为位置的比较 idx[] P1映射后的数组 idx[] = [2, 1, 0, 3, 4] 找到最长递增子序列的长度就是最终答案 (LIS在上一节) idx[]的最长递增子序列[1, 3, 4] 也就对应的原数组[2, 4, 5] 这些元素的顺序也相同及它们的 公共子序列答案为3 |
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include <bits/stdc++.h> using namespace std; void solve() { int n; cin >> n; vector < int > idx(n + 1, 0), a(n), b(n), res(n + 1, 0), ans; for ( int i = 0; i < n; i++) { cin >> a[i]; } for ( int i = 0; i < n; i++) { cin >> b[i]; idx[b[i]] = i; } for ( int i = 1; i <= n; i++) { res[i] = idx[a[i - 1]]; } for ( int i = 1; i <= n; i++) { // LIS (最长递增子序列的板子) if (ans.empty() || res[i] > ans.back()) { ans.emplace_back(res[i]); } else { auto it = lower_bound(ans.begin(), ans.end(), res[i]) - ans.begin(); ans[it] = res[i]; } } cout << ans.size() << '\n' ; } int main() { cin.tie(0) -> sync_with_stdio( false ); int t = 1; // cin >> t; while (t--) { solve(); } return 0; } |
扩展2: 计算到达最长公共子序列长度的方案数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 方案数其实很好考虑 if X[i] == Y[j]: cnt[i][j] = cnt[i - 1][j - 1] else : cnt[i][j] = cnt[i - 1][j] + cnt[i][j - 1] 这里唯一的坑就是去重复 那么接下来我们开始去重复 if X[i] == Y[j]: cnt[i][j] += (cnt[i - 1][j - 1] == 0 ? 1 : cnt[i - 1][j - 1]); else : if (dp[i][j] == dp[i - 1][j]): cnt[i][j] += cnt[i - 1][j]; if (dp[i][j] == dp[i][j - 1]): cnt[i][j] += cnt[i][j - 1]; if (dp[i][j] == dp[i - 1][j - 1]): // 唯一的坑 避免重复计数 cnt[i][j] -= cnt[i - 1][j - 1]; |
Code1:(二维数组):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | #include <bits/stdc++.h> using namespace std; constexpr int mod = 100000000; void solve() { string s, t; cin >> s >> t; s = s.substr(0, s.find( '.' )); t = t.substr(0, t.find( '.' )); int n = s.size(), m = t.size(); vector <vector < int >> dp(n + 1, vector < int > (m + 1, 0)), cnt(n + 1, vector < int > (m + 1, 0)); for ( int i = 0; i <= n; i++) { cnt[i][0] = 1; } for ( int j = 0; j <= m; j++) { cnt[0][j] = 1; } for ( int i = 1; i <= n; i++) { for ( int j = 1; j <= m; j++) { dp[i][j] = max({dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1] + (s[i - 1] == t[j - 1])}); } } for ( int i = 1; i <= n; i++) { for ( int j = 1; j <= m; j++) { auto &val = cnt[i][j]; if (s[i - 1] == t[j - 1]) { if (dp[i][j] == dp[i - 1][j - 1] + 1) { val = (val + cnt[i - 1][j - 1]) % mod; } } if (dp[i][j] == dp[i - 1][j]) { val = (val + cnt[i - 1][j]) % mod; } if (dp[i][j] == dp[i][j - 1]) { val = (val + cnt[i][j - 1]) % mod; } if (s[i - 1] != t[j - 1]) { if (dp[i][j] == dp[i - 1][j - 1]) { val = ((val - cnt[i - 1][j - 1]) + mod) % mod; } } } } cout << dp[n][m] << '\n' ; cout << cnt[n][m] << '\n' ; } int main() { cin.tie(0) -> sync_with_stdio( false ); int t = 1; // cin >> t; while (t--) { solve(); } return 0; } |
Code2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | #include <bits/stdc++.h> using namespace std; using i64 = int64_t; constexpr int N = 5e3 + 5, mod = 1e8; int dp[2][N], cnt[2][N]; string s, t; void solve() { cin >> s >> t; int n = s.size() - 1, m = t.size() - 1; for ( int i = 0; i <= m; i++) { cnt[0][i] = 1; } cnt[1][0] = 1; for ( int i = 1; i <= n; i++) { int now = i & 1, pre = now ^ 1; for ( int j = 1; j <= m; j++) { dp[now][j] = max(dp[pre][j], dp[now][j - 1]); if (s[i - 1] == t[j - 1]) { dp[now][j] = max(dp[now][j], dp[pre][j - 1] + 1); } cnt[now][j] = 0; if (s[i - 1] == t[j - 1] && dp[now][j] == dp[pre][j - 1] + 1) { cnt[now][j] = (cnt[now][j] + cnt[pre][j - 1]) % mod; } if (dp[now][j] == dp[pre][j]) { cnt[now][j] = (cnt[now][j] + cnt[pre][j]) % mod; } if (dp[now][j] == dp[now][j - 1]) { cnt[now][j] = (cnt[now][j] + cnt[now][j - 1]) % mod; } if (s[i - 1] != t[j - 1] && dp[now][j] == dp[pre][j - 1]) { cnt[now][j] = ((cnt[now][j] - cnt[pre][j - 1]) + mod) % mod; } } } cout << dp[n & 1][m] << '\n' ; cout << cnt[n & 1][m] % mod << '\n' ; } int main() { cin.tie(0) -> sync_with_stdio( false ); int t = 1; // cin >> t; while (t--) { solve(); } return 0; } |