最长公共子序列

最长公共子序列

LCS是指这两个序列中最长的公共子序列, 子序列:不要求字符在原序列中连续, 但相对顺序必须保持一致

问题:

给定两个字符串X和Y, 我们需要找到它们最长公共子序列 
X = "ABCBDAB" Y = "BDCAB"
输出最长公共子序列的长度为4 及"BDAB"

动态规划的思路

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:

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];
    }
};

 

降至一维 

通过观察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:

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 【模板】最长公共子序列

如何转换请看如下 (根据下面这一句话):

最长公共子序列是指在两个序列中,按照顺序能找到的最大长度的相同子序列。重点是顺序要一致
在此题中 每个数字不重复, 也就意味着一一对应
比如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:

#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: 计算到达最长公共子序列长度的方案数

方案数其实很好考虑

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:(二维数组):

#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: 

#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;
}

  

 

posted @ 2024-10-08 11:51  Iter-moon  阅读(54)  评论(0)    收藏  举报