LCS问题动态规划方法的改进:时间复杂度O(mn*(min(m,n))),空间复杂度O(1)

LCS问题,即求两个字符串的最长公共子序列的问题。该问题常用的解法有普通递归法和动态规划法。

  • 普通递归法方法采用了减而治之和分而治之的思想。但该算法存在大量子问题的重复计算,其时间复杂度为指数时间复杂度。
  • DP方法使用一个二维数组记录每个子问题的结果,从而避免了子问题的重复计算,只需要根据一定的次序,如从底向上,从只有一个字符出发,一次填满该数组,最后的DP[m][n]即为该问题的结果,同时可以根据DP数组将一个解结果还原。其时间复杂度和空间复杂度都为 \(\Theta(mn)\)。

在对DP数组观察的基础上,笔者对动态规划法进行了改进。现一个具体实例来讲解如何改进动态规划法。
两个字符串分别为"EDUCATIONAL"和"ADVANTAGE",求这两个字符串的最长公共子序列,其DP数组如下图所示:
LCS问题动态规划方法的改进:时间复杂度O(mn*(min(m,n))),空间复杂度O(1)
可以观察到最长公共子序列的一个解为DATA,对应的相同的字母,以A为例,是A在第一个字符串和第二个字符串的位置和最小的这个字母,对于剩下的字母也是如此,因此我们可以采用这样的策略来找到个一个LSC的解,即寻找两个字符串中相同的字符,记录下该字符在两个字符串中的位置,当这两个位置和最小时,该字符就是最长公共子序列的第一个字符,记录下这个字符的坐标,用ir和jr表示,第二次循环从ir+1和jr+1处开始搜索,得到最长公共子序列的第二个字符,依次类推。
编写程序的过程中,需要考虑到重复的字母,这里使用了一个函数来统计该字母在LSC解中,出现的次数,来查找该字母在两个字符串中的位置。
循环结束的条件是两个坐标之和有一个初始值minpos,当该minpos的初始值不改变时,说明寻找不到新的满足条件的字母,则循环终止。其时间复杂度为\(\O(mn*min(n,n))\),空间复杂度为\(\O(1)\),程序如下:

// LCS_DP_improve.cpp -- 优化LCS的DP解法,使空间复杂度为常数复杂度,同时得到LCS的组成
// 课后作业3
// 空间复杂度O(1)时间复杂度O(mn*min(m,n))
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
using std::string;

int timeschar(string s, char ch);               // 计算ch在s中出现的次数
int searchpos(string s, char ch, int n);        // 计算第n个ch出现的位置
int LCS_DP_improve(string A, string B, string & C);

int main()
{
    using namespace std;
    string str1 = "immaculate";
    string str2 = "computer";
    string str;
    int len = LCS_DP_improve(str1, str2, str);
    cout << str << endl;
    cout << "Length: " << len << endl;
    return 0;
}
int timeschar(string s, char ch)
{
    int len = s.size();
    int times = 0;
    for (int i = 0; i < len; i++)
        if (s[i] == ch)
            times++;
    return times;
}
int searchpos(string s, char ch, int n)
{
    int len = s.size();
    int timesch = 0;
    for (int i = 0; i < len; i++)
        if (s[i] == ch)
        {
            timesch++;
            if (timesch == n)
                return i;
        }
    return -1;          // 没有找到,本问题不存在
}
int LCS_DP_improve(string A, string B, string & C)
{
    int lenA = A.size();
    int lenB = B.size();
    int minlen = lenA > lenB ? lenB : lenA; // 计算A和B的最小长度
    int ir = -1;     // 每次喜欢开始的位置
    int jr = -1;     // 同ir
    string tempA;    // 记录每次寻找的位置
    for (int k = 0; k < minlen; k++)
    {
        int minpos = lenA + lenB;         // 记录相等时最小的x,y的坐标,初始为最大值
        char ch;    //记录找到的字母
        for (int i = ir + 1; i < lenA; i++)
            for (int j = jr + 1; j < lenB; j++)
                if(A[i] == B[j])
                {
                    if (minpos > i + j)
                    {
                        minpos = i + j;
                        ch = A[i];
                    }
                }
        if (minpos == lenA + lenB)      // 终止循环的条件
            break;
        C.push_back(ch);
        int timech = timeschar(C, ch);
        ir = searchpos(A, ch, timech);
        jr = minpos - ir;
    }
    return C.size();
}
上一篇:WebJars统一管理静态资源


下一篇:LeetCode 213 打家劫舍II