赞
踩
子序列系列应该是整个动态规划里最长的系列,大概可以分为连续子序列、不连续子序列、编辑距离和回文子序列四个模块,因此也罕见地进行划分吧。
LeetCode:最大子序和
经典题目,可以用贪心、动态规划、分而治之等办法解决。
使用动态规划时,则是以nums[i]结尾的最大数组和,要么接入上一个,要么自己从头开始。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int max_val=nums[0];
for(int i=1;i<nums.size();++i)
{
nums[i] = max(nums[i],nums[i-1]+nums[i]);
max_val = max(max_val,nums[i]);
}
return max_val;
}
};
LeetCode:最长连续递增序列
以i结尾,要么接入i-1,要么自己作为起点。
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
vector<int> dp(nums.size(),1);
int max_length=1;
for(int i=1;i<nums.size();++i)
{
if(nums[i]>nums[i-1])
dp[i]=dp[i-1]+1;
max_length=max(dp[i],max_length);
}
return max_length;
}
};
LeetCode:最长重复子数组
因为涉及两个序列,因此应该使用二维dp数组进行状态递推。
dp[i][j]代表着【nums[i]】结尾与【nums2[j]】结尾的子数组的公共长度。
理论上额外添加一行一列可以轻松递推过程,否则就需要和下面代码一样,对第一行与第一列进行初始化。
class Solution { public: int findLength(vector<int>& nums1, vector<int>& nums2) { //dp[i][j]代表着【nums[i]】结尾与【nums2[j]】结尾的子数组的公共长度 //显然,nums[i]=nums[j]时,dp[i][j]=1+dp[i-1][j-1] vector<vector<int>> dp(nums1.size(),vector<int>(nums2.size(),0)); //初始化dp[0][j]和dp[i][0] int i,j; int max_length=0; //初始化行 for(j=0;j<nums2.size();++j) { if(nums2[j]==nums1[0]) { dp[0][j]=1; max_length=1; } } //初始化列 for(i=0;i<nums1.size();++i) { if(nums1[i]==nums2[0]) { dp[i][0]=1; max_length=1; } } //动态规划 for(i=1;i<nums1.size();++i) { for(j=1;j<nums2.size();++j) { if(nums1[i]==nums2[j]) { dp[i][j]=1+dp[i-1][j-1]; max_length=max(max_length,dp[i][j]); } } } return max_length; } };
LeetCode:最长公共子序列
依然是二维dp数组,不过区别在于因为可以删除,所以递推公式,在字符不相等时,只需要考虑dp[i-1][j]和dp[i][j-1],即删除str1[i]或str2[j]。
class Solution { public: int longestCommonSubsequence(string text1, string text2) { vector<vector<int>> dp(text1.size(),vector<int>(text2.size(),0)); int i,j; //初始化 bool flag=false; for(j=0;j<text2.size();++j) { if(flag || text1[0]==text2[j]) { dp[0][j]=1; flag=true; } } flag=false; for(i=0;i<text1.size();++i) { if(flag || text1[i]==text2[0]) { dp[i][0]=1; flag=true; } } //动态规划 for(i=1;i<text1.size();++i) { for(j=1;j<text2.size();++j) { if(text1[i]==text2[j]) dp[i][j]=1+dp[i-1][j-1]; else dp[i][j]=max(dp[i-1][j],dp[i][j-1]); } } return dp.back().back(); } };
LeetCode:不相交的线
本质上与2.1完全一致的一道题,将这道题抽象为LCS即可。
在这里,我使用了[size1+1][size2+1]大小的dp数组,简化了初始化过程。
class Solution { public: int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) { vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0)); //dp[i][j]=1+dp[i-1][j-1] or dp[i][j]=max(dp[i-1][j],dp[i][j-1]) for(int i=1;i<=nums1.size();++i) { for(int j=1;j<=nums2.size();++j) { if(nums1[i-1]==nums2[j-1]) dp[i][j]=1+dp[i-1][j-1]; else dp[i][j]=max(dp[i-1][j],dp[i][j-1]); } } return dp.back().back(); } };
LeetCode:最长递增子序列
dp[i]设定为以i结尾的最长严格递增子序列的长度,每次都从前i-1个子序列里找到比nums[i]小且最长的子序列。
class Solution { public: int lengthOfLIS(vector<int>& nums) { vector<int> dp(nums.size(),1); int max_length=1; for(int i=1;i<nums.size();i++) { for(int j=0;j<i;++j) { if(nums[j]<nums[i]) dp[i] = max(dp[i],dp[j]+1); } max_length=max(max_length,dp[i]); } return max_length; } };
LeetCode:判断子序列
dp[i][j]指s[0-i]在t[0-i]的子序列中的最长公共部分,那么从定义出发dp[i][j]=dp[i][j-1]或dp[i][j]=1+dp[i-1][j-1]。
class Solution { public: bool isSubsequence(string s, string t) { //dp[i][j]指s[0-i]在t[0-i]的子序列中的最长公共部分 //dp[i][j]=dp[i][j-1] or dp[i][j]=1+dp[i-1][j-1] vector<vector<int>> dp(s.size()+1,vector<int>(t.size()+1,0)); for(int i=1;i<=s.size();++i) { for(int j=1;j<=t.size();++j) { if(s[i-1]==t[j-1]) dp[i][j]=1+dp[i-1][j-1]; else dp[i][j]=dp[i][j-1]; } } return dp[s.size()][t.size()]==s.size(); } };
LeetCode:不同的子序列
dp[i][j]代表字符串s从0~i-1的子序列中出现t[0:j-1]的次数。
class Solution { public: int numDistinct(string s, string t) { //dp[i][j]代表s到i-1处的子序列中出现t[0:j-1]的个数 //对于dp[i][j]而言,如果s[i-1]==t[j-1],那么存在两种情况 //使用s[i-1],那么dp[i][j] : dp[i-1][j-1] //不使用s[i-1],那么dp[i][j] : dp[i-1][j](即删除s[i-1]的话,到i-1的可能性就相当于i-2了) //初始化的情况下:dp[i][0]意味着s到i-1处,出现空字符的可能性,必然dp[i][0] = 1(只有一种办法,就是全部删除干净) //dp[0][j]代表空字符去匹配t[0:j-1]的可能性,必然为0 //对于特殊点dp[0][0],空字符==空字符,dp[0][0]=1 //初始化行 vector<vector<uint64_t>> dp(s.size()+1,vector<uint64_t>(t.size()+1,0)); //初始化列 for(int i=0;i<=s.size();i++) dp[i][0]=1; for(int i=1;i<=s.size();++i) { for(int j=1;j<=t.size();++j) { if(s[i-1]==t[j-1]) dp[i][j] = dp[i-1][j-1]+dp[i-1][j]; else dp[i][j] = dp[i-1][j]; } } return dp[s.size()][t.size()]; } };
LeetCode:两个字符串的删除操作
遇上一题的思路完全一致。
class Solution { public: int minDistance(string word1, string word2) { //Delete=L1+L2-2*L3,显然剩余部分L3最长时,Delete最小 //dp[i][j]代表使得word1[0:i-1]和word2[0:j-1]所需的最小删除次数 vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0)); //初始化 int i,j; //要和空字符串一致,则有多少个字符删多少个 for(i=1;i<=word1.size();++i)dp[i][0]=i; for(j=1;j<=word2.size();++j)dp[0][j]=j; //动态规划 for(i=1;i<=word1.size();++i) { for(j=1;j<=word2.size();++j) { //字符一样的话,就和前一位一致 if(word1[i-1]==word2[j-1]) dp[i][j]=dp[i-1][j-1]; //不一样的话,就考虑删word1或者删word2 //不全删的原因在于,dp[i-1][j]或者dp[i][j-1]的计算过程中,已经考虑到了dp[i-1][j-1] else dp[i][j]=1+min(dp[i-1][j],dp[i][j-1]); } } return dp[word1.size()][word2.size()]; } };
LeetCode:编辑距离
和上一题没有本质区别,需要认识到两点。
class Solution { public: int minDistance(string word1, string word2) { //dp[i][j]代表word1[0:i-1]到word2[0:j-1]的最少操作数 //当word1[i-1]=word2[j-1]时,不需要额外操作,dp[i][j]=dp[i-1][j-1] //当不相等时,考虑操作替换/插入/删除 int length1=word1.size(); int length2=word2.size(); int i,j; vector<vector<int>> dp(length1+1,vector<int>(length2+1,0)); //初始化 for(i=1;i<=length1;++i) dp[i][0]=i; for(j=1;j<=length2;++j) dp[0][j]=j; //递推 for(i=1;i<=length1;++i) { for(j=1;j<=length2;++j) { if(word1[i-1]==word2[j-1]) dp[i][j]=dp[i-1][j-1]; else dp[i][j]=1 + min(dp[i-1][j-1] , min(dp[i-1][j], dp[i][j-1])); } } return dp[length1][length2]; } };
LeetCode:回文子串
动态规划的思想很简单,dp[i][j]代表s[i]~s[j]是否是回文,需要注意两点:
另外也可以使用双指针解决,任何回文子串都可以由长度为1或长度为2的回文子串扩散得到。
class Solution { public: int countSubstrings(string s) { int size=s.size(); //dp[i][j]代表s[i]~s[j]是否为回文子串,其中j>=i //一般情况下dp[i][j]=dp[i+1][j-1] && s[i]==s[j],即依赖左下角的值,所以应该从下到上从左到右遍历 vector<vector<bool>> dp(size,vector<bool>(size,false)); int count=0; //从下到上 for(int i=size-1;i>=0;--i) { //从左到右 for(int j=i;j<size;++j) { //字符一样是大前提 if(s[i]==s[j]) { //长度<=2 if(j<=i+1) dp[i][j]=true; //长度>=3 else dp[i][j]=dp[i+1][j-1]; } //如果回文 if(dp[i][j]) ++count; } } return count; }
class Solution { public: int diffuse(string& s,int left,int right) { int res=0; while(left>=0 && right<s.size() && s[left]==s[right]) { ++res; --left; ++right; } return res; } int countSubstrings(string s) { int result=0; for(int i=0;i<s.size();++i) { result+=diffuse(s,i,i); result+=diffuse(s,i,i+1); } return result; } };
LeetCode:最长回文子序列
dp[i][j]记录s[i~j]的回文子序列的最大长度。
class Solution { public: int longestPalindromeSubseq(string s) { int size=s.size(); int max_length=0; //dp[i][j]s[i~j]的回文子序列的最大长度 vector<vector<int>> dp(size,vector<int>(size,0)); for(int i=size-1;i>=0;i--) { for(int j=i;j<size;j++) { if(s[i]==s[j]) { //长度<=2 if(j-i<=1) dp[i][j]=j-i+1; else dp[i][j]=dp[i+1][j-1]+2; } //不相等时 else dp[i][j] = max(dp[i+1][j],dp[i][j-1]); max_length=max(max_length,dp[i][j]); } } return max_length; } };
动态规划,完结撒花!另外leetcode的基础题也基本刷完一遍了,接下来要花更多的功夫学计算机基础相关知识,以及更难的一些数据结构(AVL、红黑数、B树、B+树、哈夫曼树、图等等)与算法(深入了解DFS和BFS)。
——2023.3.6
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。