最长公共子序列(LCS)

otto

最近刚好算法实验做了这个, 水一篇博客吧

算法思路

基本分析:

假设字符串A的长度为m, 字符串B的长度为n, 使用一个数组fm表示对应结果, 其中f[i][j]表示 A.substr(0, i)B.substr(0, j)这两个子字符串的LCS.

考虑f[i][j], 他总是可以从上方⬆ (即不使用A的第i个字符)和 左方⬅(不使用B的第j个字符)两个方向转移过来, 因此有f[i][j] = max(f[i – 1][j], f[i][j – 1]);

此外, 如果A[i] == B[j], 那么它可以从左上方转移过来, 此时有f[i][j] = max(f[i][j], f[i – 1][j – 1] + 1);

空间优化:

上述分析已经可以完整地解决LCS问题, 想求出LCS只需要在转移的时候进行记录就行, 不过空间复杂度是O(mn)的, 然后通过我们的分析过程可以看到, 转移的时候只涉及了当前行和上一行(这里假设行size更小, 如果列size更小的话同理).

因此我们可以用两个长度为m + 1的数组, 通过不停更新来完成这个算法.

空间进一步优化:

经过上一步优化之后所用空间为两个长度为 m + 1的数组, 以及常数个额外变量. 不过还可以更进一步优化, 因为自始至终我们转移到[i][j]的时候只和三个位置的值有关: 正上方[i – 1][j], 左方[i][j – 1]和左上方[i – 1][j – 1].

因此我们可以考虑用一个数组来表示, 在只使用一个数组的情况下, 更新前f[j]就是上一轮更新后的上方f[i – 1][j], 而f[j – 1]也就是左方f[i][j – 1], 也就是说我们在更新f[j]的时候, 需要的三个值中的两个都在这个数组中了, 那么我们只需要缓存f[i – 1][j – 1]就行了. 这种情况下, 空间为一个长度为m + 1的数组, 加上两个额外变量. 空间复杂度较低.

代码

朴素:

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int n1 = text1.size(), n2 = text2.size();
        vector<vector<int>> dp(n1 + 1, vector<int> (n2 + 1, 0));
        for (int i = 1; i <= n1; ++i) {
            for (int j = 1; j <= n2; ++j) {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); // 从正上方和左方转移
                if (text1[i - 1] == text2[j - 1]) {
                    dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1); // 从斜上方转移
                }
            }
        }
        return dp[n1][n2];
    }
};

两个数组:

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int n1 = text1.size(), n2 = text2.size();
        int minlen = min(n1, n2), maxlen =max(n1, n2); // 使用较小的长度作为数组长度
        vector<int> pre(minlen + 1, 0);
        vector<int> cur(minlen + 1, 0);
        for (int i = 1; i <= maxlen; ++i) {
            for (int j = 1; j <= minlen; ++j) {
                cur[j] = max(cur[j - 1], pre[j]); // 从上方和左方转移
                // 从左上方转移
                if (n1 > n2 && text1[i - 1] == text2[j - 1]) {
                    cur[j] = max(cur[j], pre[j - 1] + 1);
                } 
                if (n1 <= n2 && text2[i - 1] == text1[j - 1]) {
                    cur[j] = max(cur[j], pre[j - 1] + 1);
                }
            }
            // 更新pre
            for (int j = 1; j <= minlen; ++j) {
                pre[j] = cur[j];
            }
        }
        return cur[minlen];
    }
};

一个数组:

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int n1 = text1.size(), n2 = text2.size();
        int minlen = min(n1, n2), maxlen = max(n1, n2);
        vector<int> cur(minlen + 1, 0); // 使用一个数组实现
        for (int i = 1; i <= maxlen; ++i) {
            int pre = 0;
            for (int j = 1; j <= minlen; ++j) {
                int nextPre = cur[j];
                cur[j] = max(cur[j - 1], cur[j]);  // 从上方和左方转移, 本身就是上方
                // 从左上方转移
                if (n1 > n2 && text1[i - 1] == text2[j - 1]) {
                    cur[j] = max(cur[j], pre + 1);
                } 
                if (n1 <= n2 && text2[i - 1] == text1[j - 1]) {
                    cur[j] = max(cur[j], pre + 1);
                }
                // 更新f[i - 1][j - 1]
                pre = nextPre;
            }
        }
        return cur[minlen];
    }
};
阅读 75
1 声望
0 粉丝
0 条评论
1 声望
0 粉丝
宣传栏