最长回文子序列LCS,最长递增子序列LIS及相互联系

最长公共子序列LCS

Lintcode 77. 最长公共子序列
LCS问题是求两个字符串的最长公共子序列

[ dp[i][j] = left{egin{matrix} & max(dp[i-1][j], dp[i][j-1]), s[i] != s[j]\ & dp[i-1][j-1] + 1, s[i] == s[j] end{matrix} ight. ]

许多问题可以变形为LCS问题以求解

class Solution {
public:
    /**
     * @param A: A string
     * @param B: A string
     * @return: The length of longest common subsequence of A and B
     */
    int longestCommonSubsequence(string &A, string &B) {
        // write your code here
        int n = A.size();
        int m = B.size();
        std::vector<vector<int>> dp(m+1, vector<int>(n+1, 0)) ;
        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
               
                if(A[i-1] == B[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
                else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
              
            }
        }
        
        return dp[m][n];
    }
};

因为dp[i][j]仅仅用到了i-1和i层的数据,因此可以用滚动数组来压缩空间,使得空间复杂度为(O(min(m,n)))

class Solution {
public:
    /**
     * @param A: A string
     * @param B: A string
     * @return: The length of longest common subsequence of A and B
     */
    int longestCommonSubsequence(string &A, string &B) {
        // write your code here
        int n = A.size();
        int m = B.size();
        std::vector<vector<int>> dp(2, vector<int>(n+1, 0)) ;
        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
               
                if(A[i-1] == B[j-1]) dp[i%2][j] = dp[(i-1)%2][j-1] + 1;
                else dp[i%2][j] = max(dp[(i-1)%2][j], dp[i%2][j-1]);
              
            }
        }
        
        return dp[m%2][n];
    }
};

最长递增子序列LIS

300. Longest Increasing Subsequence

动态规划

可以假定dp[i]为以nums[i]结尾的LIS长度,则dp[i] = max(dp[j] + 1)( j<i 且 nums[j] < nums[i]), 时间复杂度为(O(n^2)),时间复杂度为(O(n))

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n, 1);
        int MAX = 0;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < i; j++){
                if(nums[i] > nums[j]) dp[i] = max(dp[j] + 1, dp[i]);
            }
            MAX = max(MAX, dp[i]);
        }
        
        return MAX;
    }
};

贪心+二分

首先我们设置一个辅助数组v,其中v[i]表示长度为i-1的LIS的末尾值,首先扫描原数组,当处理到nums[i]时和v中的数据比较,二分查找最后一个比nums[i]小的值,并更换,如果不存在,则加入到末尾,v最后的长度就是原数组LIS的长度,时间复杂度(nlgn)

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> v;
        
        for(int i = 0; i < n; ++i){
            auto loc = lower_bound(v.begin(), v.end(), nums[i]);
            if(loc == v.end()) v.push_back(nums[i]);
            else *loc = nums[i];
        }
        
        return v.size();
    }
};

如果仅仅是求LIS长度和允许改变原数组,空间复杂度可降低为(O(1))

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        auto p = nums.begin();
        auto q = nums.begin();
        
        for(int i = 0; i < n; i++){
            auto r = lower_bound(p, q, nums[i]);
            if(r == q){ ++q; }
            *r = nums[i];
        }
        
        return q - p;
    }
};

LIS与LCS的相互转化

LIS问题可以变形为LCS问题,如输入数组为[5,1,4,2,3],最长递增子序列为[1,2,3],可以先将原数组排序得到一个新数组[1,2,3,4,5],然后新数组与原数组作为LCS的输入求解, 时间复杂度为(O(n^2)), 空间复杂度为(O(n^2))

LCS问题也可变为LIS问题,假定输入数组为数字数组如A=[1,7,5,4,8,3,9], B=[1,4,3,5,6,2,8,9],且在A,B两个序列中每个元素各不相同(如1-n的排列),如果使用LCS求解最长公共子序列长度,则复杂度为(O(n^2)),A,B两个序列中每个元素各不相同,因此我们可以将A重新编码A=[1,2,3,4,5,6,7](编码不重复), B可以编码为B=[1,4,6,3,0,0,5,7](0表示不存在,也可以直接删除),然后求重新编码后A,B的LIS长度,时间复杂度为(O(nlgn))

LCS与最长回文子序列LPS及变种

求S的最长回文子序列也可以使用LCS的思想,先将S反转得到S',然后求LCS(S,S')
leetcode 516. Longest Palindromic Subsequence

class Solution {
public:
    int lcs(string &A, string &B) {
        int n = A.size();
        int m = B.size();
        std::vector<vector<int>> dp(m+1, vector<int>(n+1, 0)) ;
        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
               
                if(A[i-1] == B[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
                else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
              
            }
        }
        
        return dp[m][n];
    }
    int longestPalindromeSubseq(string s) {
        string t(s.rbegin(), s.rend()); 
        return lcs(s, t);
    }
};

变种:求在S中任何位置插入或删除最少字符个数使得S成为回文串
解法:先求最长回文子序列,然后用原长度-LPS长度

不求长度求原序列

参考

原文地址:https://www.cnblogs.com/qbits/p/11230679.html