LeetCode 1143. 最长公共子序列(C++)

题目地址:力扣

题目难度:Medium

涉及知识点:动态规划、字符串匹配


分析:由于子序列不同于子串,子串必须要连续,而子序列可以不连续。假设最长子序列长度为k,那么我们如果要通过遍历的方法来暴力求解,其时间复杂度至少为O(n^k),这肯定是无法接受的。这道题是一道典型的动态规划问题。

解法1:动态规划

思路:动态规划的思想就是后面的状态可以利用前面已经得出的结果从而实现状态转移。

想一想我们假设有两个字符串,分别是"ab"和"ab",那么这两个字符串的最长公共子序列显然是"ab"。那我们怎么考虑这个问题呢,假设这两个字符串都只考虑最前面的一个字符,即"a"和"a",那么此时最长公共子序列是"a"。而看当前状态,即第二个字符"b"和"b"相等,因此我们可以得出若当前比较的字符相等时,最长公共子序列长度等于往前看一个字符的最长公共子序列长度加1,这个不需要证明,显然可以得出这样的结论。

我们再考虑一种情况,即两个字符串分别是"ab"和"ad",这两个字符串的最长公共子序列显然是"a",似乎也只需要往前看一个字符。但是我们再考虑字符串"acdb"与字符串"cdba",这两个字符串的最长公共子序列显然是“cdb”。但若我们只往前看一位的话,会发现字符串"acd"和字符串"cdb"的最长公共子序列为"cd",与之前得出的子序列并不相同。这是因为,若当前比较的字符不同时,两个字符串并不产生对称关系。那么怎么才能得出这两者的最长公共子序列呢?依旧是要借助前面的状态,但是由于两者不对称,因此我们需要在保持一个字符串长度不变的同时,看与另一个字符串少一位的公共子序列。也就是说,我们需要分别看"acdb"与"cdb"和"acd"与"cdba"的最长公共子序列。通俗的说,就是我知道两个字符串的这一位肯定不匹配了,但是我需要看一下其中一个字符串中的这一位是否能和另一个字符串这位之前的部分匹配上。并且取两者中匹配的最大者作为最长公共子序列。

显然我们需要一个二维的动态规划数组dp[i][j]来表示这样的操作,该数组记录的是最长公共子序列的长度。由于两个字符串的长度并不是总是相等的,那么我们用i和j分别来代表当前比较的位置,是第一个字符串的i位置与第二个字符串的j位置字符进行比较。根据str1[i]和str2[j]是否相等的关系,我们可能会需要依赖于dp[i-1][j-1];也可能会依赖于max(dp[i-1][j], dp[i][j-1]),而我们采用迭代的方式就必须保证当前步可能要用到的之前所有步都已经被计算出来。若我们用二维表格的代替dp数组,我们会发现,点(i, j)可能会依赖于其上边(i-1, j),左边(i, j-1),以及左上方(i-1, j-1)的值,因此我们需要从头开始迭代来计算出表格中的每一个数值,最后取右下角的数值即为最终的结果。

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        // 记录字符串长度,开一个长度为有sz1+1行, sz2+1列的数组记录状态
        int sz1 = text1.size(), sz2 = text2.size();
        vector> dp(sz1+1, vector(sz2+1));
        // 为了方便判断,我们把dp[0][sz2]和dp[sz1][0]全部初始化为0
        // 而数组下标的1来作为字符串的第一个字符存放位置
        for (int i = 0; i < sz1; ++i)
        {
            for(int j = 0; j < sz2; ++j)
            {
                if (text1[i] == text2[j]) {
                    dp[i+1][j+1] = dp[i][j]+1;
                } else {
                    dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j]);
                }
            }
        }
        return dp[sz1][sz2];
    }
};

注意:在上面的代码中,我们开辟的数组行和列都比实际的长度要多一位,这是因为我们比较两个字符串第一位dp[0][0]的时候也需要利用上一个状态,而上一个状态有可能是dp[-1][-1], dp[-1][0], dp[0][-1]。无论是哪个,我们都知道其中一个比较的字符串为空,因此最长公共子序列长度肯定为0。为了避免特判,因此我们可以用空间来换时间,将字符串第一位的比较放在dp[1][1]中,而dp[0][x]与dp[x][0]全部置为0(其中x是从0开始不超过当前维度的任意数)。

Accepted

  • 45/45 cases passed (16 ms)
  • Your runtime beats 93.35 % of cpp submissions
  • Your memory usage beats 48.43 % of cpp submissions (12.7 MB)

你可能感兴趣的:(力扣刷题,c++,leetcode,开发语言)