📜  以 1 开始和结束并在中间填充 0 的最长子序列(1)

📅  最后修改于: 2023-12-03 15:06:39.940000             🧑  作者: Mango

最长子序列问题

最长子序列问题是一类经典的动态规划问题,特别需要留意的有最长公共子序列问题(LCS,Longest Common Subsequence)以及最长上升子序列问题(LIS,Longest Increasing Subsequence)。

最长公共子序列问题

最长公共子序列问题是指给定两个字符串S和T,求它们的最长公共子序列。比如S=”BDCABA”和T=”ABCBDAB”,这两个字符串的最长公共子序列是”BCBA”,长度为4。

解法

解决LCS问题常常有两种方案:

  • 空间换时间:构造一个表,用动态规划的方式填写表格,然后读取表格。
  • 时间换空间:回溯法搜索所有的LCS,找出最大长度。

以空间换时间的方式为例,构造一个二维表dp[0…n][0…m],并令所有dp[i][0]以及dp[0][j]均为0。然后用线性的方式按顺序填写表格,并记下从表格中读取的结果。生成的最长公共子序列就是所有在填充表格中生成的矩形内的字符。

代码

下面是一个递归方式的LCS实现,使用记忆化搜索优化。该实现虽然不如标准方法的运行时间优化,但对于LCS问题的理解至关重要。

int LCS(string s, string t, int i, int j, unordered_map<string, int>& m) {
    if(i == -1 || j == -1) return 0;
    string key = to_string(i) + '|' + to_string(j);
    if(m.count(key)) return m[key];
    if(s[i] == t[j]) return m[key] = LCS(s, t, i-1, j-1, m) + 1;
    return m[key] = max(LCS(s, t, i-1, j, m), LCS(s, t, i, j-1, m));
}

int longestCommonSubsequence(string s, string t) {
    unordered_map<string, int> m;
    return LCS(s, t, s.size()-1, t.size()-1, m);
}
最长上升子序列问题

最长上升子序列问题是指给定一个序列S,找出一个最长的子序列使得其中的元素满足单调递增。比如S={10,9,2,5,3,7,101,18}的最长上升子序列是{2,3,7,101}。

解法

常常使用动态规划来解决。设C[i]为所有长度为i的上升子序列中,最后一个数字最小的序列的最后一个元素,dp[i]则表示长度为i的上升子序列的最后一个数字的最小值。

初始情况下,C[0]=INT_MIN,dp[0]=-1。在每个位置i,使用二分搜索来找到满足j<i且C[j]<S[i]的j,之后更新C[j+1]=S[i],同时更新dp[j+1]=i。

代码
int lengthOfLIS(vector<int>& nums) {
    if(nums.empty()) return 0;
    vector<int> dp(nums.size()+1, -1);
    vector<int> C(nums.size()+1, INT_MAX);
    C[0] = INT_MIN;
    int len = 0;
    for(int i = 0; i < nums.size(); i++) {
        int pos = lower_bound(C.begin(), C.end(), nums[i]) - C.begin();
        C[pos] = nums[i];
        dp[pos] = i;
        len = max(len, pos);
    }
    return len;
}

这段代码使用长度O(nlogn)的时间复杂度解决最长上升子序列问题。