zoukankan      html  css  js  c++  java
  • 最长公共子序列及其引申问题

    最长公共子序列是经典的动态规划问题,在很多书籍和文章中都有介绍,这里对这一经典算法进行回顾并对两个follow up questions进行总结和分析。

    1. 回顾LCS(longest common subsequence)解法,求LCS长度

    典型的双序列动态规划问题,dp[i][j]表示第一个序列前i项与第二个序列的前j项....

    所以对应此题,dp[i][j]表示序列s1的前i项与序列s2的前j项的最长公共子序列。

    得到如下递推关系式:

    dp[i][j] = dp[i - 1][j - 1] + 1                  if s1[i - 1] == s2[j - 1]

        = max(dp[i - 1][j], dp[i][j - 1])   if s1[i - 1] != s2[j - 1] ;

    对dp[0][j] j = 0,1,... 于 dp[i][0] i = 0,1...初始化,根据递推关系式则可以得到结果。

    程序:

     1 int longsetCommonSubsequence(const string& s1, const string& s2) {
     2     int sz1 = s1.size(), sz2 = s2.size();
     3     int dp[sz1 + 1][sz2 + 1];
     4     for (int i = 0; i <= sz1; ++i) {
     5         dp[i][0] = 0;
     6     }
     7     for (int i = 0; i <= sz2; ++i) {
     8         dp[0][i] = 0;
     9     }
    10     for (int i = 1; i <= sz1; ++i) {
    11         for (int j = 1; j <= sz2; ++j) {
    12             if (s1[i] == s2[j]) {
    13                 dp[i][j] = dp[i - 1][j - 1] + 1;
    14             }
    15             else {
    16                 dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
    17             }
    18         }
    19     }
    20     return dp[sz1][sz2];
    21 }

    2. 如何得到最长公共子序列?

    其实本质上,当有了dp数组时,即得到了行程LCS的所有信息。

    我们考察一个例子,求s1 = "ABCBDAB" 和 s2 = "BDCABA"的 LCS, 得到其dp数组如下。(图片来自邹博

    我们可以看到,当dp数组被填充完毕后。反向探索LCS的形成过程,结合递推公式可知,每一个添加到LCS中的字符是在s1[i - 1] == s2[j - 1]的情况下加入的。

    也就是上述图中向同时向左上方的那些步骤,而对应s1[i - 1] != s2[j - 1]的那些步骤,则选择其dp值大的(原因在于生成的时候路径就是选的max)进行前进即可。

    所以总结寻找LCS的算法步骤即:

    如果s1[i - 1] == s2[j - 1],将其push_back到结果中;

    如果s1[i - 1] != s2[j - 1],选择dp[i - 1][j]与dp[i][j - 1]中大的更新i--或j--。

    直到i或者j达到0后,翻转上述结果即可。

    代码:

     1 string longsetCommonSubsequence2(const string& s1, const string& s2) {
     2     int sz1 = s1.size(), sz2 = s2.size();
     3     int dp[sz1 + 1][sz2 + 1];
     4     for (int i = 0; i <= sz1; ++i) {
     5         dp[i][0] = 0;
     6     }
     7     for (int i = 0; i <= sz2; ++i) {
     8         dp[0][i] = 0;
     9     }
    10     for (int i = 1; i <= sz1; ++i) {
    11         for (int j = 1; j <= sz2; ++j) {
    12             if (s1[i - 1] == s2[j - 1]) {
    13                 dp[i][j] = dp[i - 1][j - 1] + 1;
    14             }
    15             else {
    16                 dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
    17             }
    18         }
    19     }
    20     string result;
    21     int i = sz1, j = sz2;
    22     while (i > 0 && j > 0) {
    23         if (s1[i - 1] == s2[j - 1]) { //对应的是s1[i - 1], s2[j - 1]
    24             result.push_back(s1[i - 1]);
    25             i--;
    26             j--;
    27         }
    28         else {
    29             if (dp[i - 1][j] > dp[i][j - 1]) {
    30                 i--;
    31             }
    32             else {
    33                 j--;
    34             }
    35         }
    36     }
    37     reverse(result.begin(), result.end());
    38     return result;
    39 }

    3. 有多组解都要输出怎么办?

    首先考虑什么时候会有多组解?还考虑上述图示可以发现,当s1[i - 1] != s2[j -1]但 dp[i - 1][j] == dp[i][j - 1]时,i--, j--均可。所以可能产生多组解。

    想要输出所有解的想法其实也很直观,就是DFS,把所有情况都遍历一遍,并进行去重(代码中find语句),把不同路径的相同解删除掉,即可得到所有解。

    一段完整的,带简单测试的程序如下:

     1 #include <iostream>
     2 #include <string>
     3 #include <vector> 
     4 #include <algorithm>
     5 using namespace std;
     6 vector<int> temp;
     7 vector<vector<int>> resultIndex;
     8 void dfs(const vector<vector<int>>& dp, const string& s1, const string& s2, int i, int j) {
     9     if (i == 0 || j == 0) {
    10         if (find(resultIndex.begin(), resultIndex.end(), temp) == resultIndex.end()) {
    11             resultIndex.push_back(temp);
    12         }
    13         return ;
    14     }
    15     if (s1[i - 1] == s2[j - 1]) {
    16         temp.push_back(i - 1);
    17         dfs(dp, s1, s2, i - 1, j - 1);
    18     }
    19     else {
    20         if (dp[i - 1][j] > dp[i][j - 1]) {
    21             dfs(dp,s1,s2,i - 1, j);
    22         }
    23         else if (dp[i - 1][j] < dp[i][j - 1]) {
    24             dfs(dp, s1, s2, i, j - 1);
    25         }
    26         else {
    27             vector<int> temp2 = temp;
    28             dfs(dp,s1,s2,i - 1, j);
    29             temp = temp2;
    30             dfs(dp,s1,s2,i, j - 1);
    31         }
    32     }
    33     return;
    34 }
    35 
    36 vector<string> longsetCommonSubsequence2(const string& s1, const string& s2) {
    37     int sz1 = s1.size(), sz2 = s2.size();
    38     vector<vector<int>> dp(sz1 + 1,vector<int>(sz2 + 1));
    39     for (int i = 0; i < sz1; ++i) {
    40         dp[i][0] = 0;
    41     }
    42     for (int i = 0; i < sz2; ++i) {
    43         dp[0][i] = 0;
    44     }
    45     for (int i = 1; i <= sz1; ++i) {
    46         for (int j = 1; j <= sz2; ++j) {
    47             if (s1[i - 1] == s2[j - 1]) {
    48                 dp[i][j] = dp[i - 1][j - 1] + 1;
    49             }
    50             else {
    51                 dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
    52             }
    53         }
    54     }
    55     vector<string> result;
    56     dfs(dp,s1,s2,sz1,sz2);
    57     for (int i = 0; i < resultIndex.size(); ++i) {
    58         string s;
    59         result.push_back(s);
    60         for (int j = resultIndex[i].size() - 1; j >= 0; --j) {
    61             result[i].push_back(s1[resultIndex[i][j]]);
    62         }
    63     }
    64     return result;
    65 
    66 }
    67 
    68 int main() {
    69     string s1 = "ABCBDAB", s2 = "BDCABA";
    70     vector<string> result = longsetCommonSubsequence2(s1,s2);
    71     for (int i = 0; i < result.size(); ++i) {
    72         cout << result[i] << endl;
    73     }
    74     return 0;
    75 }
  • 相关阅读:
    【LeetCode OJ】Remove Element
    【LeetCode OJ】Remove Duplicates from Sorted Array
    【LeetCode OJ】Swap Nodes in Pairs
    【LeetCode OJ】Merge Two Sorted Lists
    【LeetCode OJ】Remove Nth Node From End of List
    【LeetCode OJ】Two Sum
    【LeetCode OJ】Majority Element
    最长公共子序列问题
    php fopen与file_get_contents的区别
    PHP 技巧集合
  • 原文地址:https://www.cnblogs.com/wangxiaobao/p/5982901.html
Copyright © 2011-2022 走看看