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

it2022-06-26  91

最长公共子序列LCS

Lintcode 77. 最长公共子序列 LCS问题是求两个字符串的最长公共子序列\[ dp[i][j] = \left\{\begin{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}\right. \] 许多问题可以变形为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长度

不求长度求原序列

参考

LIS算法: 最长上升子序列算法竞赛入门经典-例题27:王子与公主算法导论第三版思考题15-2

转载于:https://www.cnblogs.com/qbits/p/11230679.html


最新回复(0)