0
点赞
收藏
分享

微信扫一扫

《dp补卡——子序列问题》


目录

  • ​​300. 最长递增子序列​​
  • ​​674. 最长连续递增序列​​
  • ​​718. 最长重复子数组​​
  • ​​1143. 最长公共子序列​​
  • ​​53. 最大子序和​​
  • ​​392. 判断子序列​​
  • ​​115. 不同的子序列​​
  • ​​583. 两个字符串的删除操作​​
  • ​​72. 编辑距离​​
  • ​​647. 回文子串 (与 5.最长回文子串思路差不多)​​
  • ​​516. 最长回文子序列​​

300. 最长递增子序列

step1:dp[i]:下标<=i的最长子序列长度。即以 nums[i] 结尾 的「上升子序列」的长度,注意以nums[nums.size()-1]结尾的上升子序列长度并不一定是全局最优值,所以要从dp[i]中找到最大值。
step2:状态转移方程:

if(nums[i] > nums[j]) dp[i] = max(dp[i],dp[j]+1); //取得dp[j]+1中最大的那个值作为dp[i]

step3:初始化
每一个i,至少都是1.
step4:遍历顺序,从前向后遍历。

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

674. 最长连续递增序列

简单题,迅速AC。不过这个是贪心的思路。

class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
int n = nums.size();
if(n == 0) return 0;
int len=1;
int maxlen =1;
for(int i = 1; i < n; i++)
{
if(nums[i] > nums[i-1])
{
len+=1;
}
else
len = 1;
maxlen = max(maxlen,len);
}
return maxlen;
}
};

下面使用dp思路:
step1:dp[i]以下标为i结尾的数组连续递增的子序列长度为dp[i]
step2: 如果nums[i+1] > nums[i],那么以i+1结尾的连续递增的子序列长度一定等于以i结尾的数组的连续递增的子序列长度+1
即​​​dp[i+1] = dp[i] + 1​​ 否则,dp不进行赋值,同时需要注意以nums.size()-1结尾的连续递增子序列长度不一定为全局最优值。

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

718. 最长重复子数组

《dp补卡——子序列问题》_动态规划

class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
int len1 = nums1.size();
int len2 = nums2.size();
vector<vector<int>> dp(len1,vector<int>(len2,0));
int result = 0;
for(int i = 0; i < len1; i++)
{
for(int j = 0; j < len2; j++)
{
if(nums1[i] == nums2[j])
{
if(i == 0 || j == 0)
{
dp[i][j] = 1;
}
else
dp[i][j] = dp[i-1][j-1]+1;
}
result = max(result,dp[i][j]);
}
}
return result;
}
};

1143. 最长公共子序列

这里不要求子序列是连续的,但是要相对有顺序。
step1:
​​​dp[i][j]​​​:长度为[0,i]的字符串text1与长度为[0,j]的字符串text2的最长公共子序列长度
step2:

if(text1[i] == text[j]) 
{
//找到了一个公共元素
dp[i][j] = dp[i-1][j-1]+1;
}
else
{
//没有找到公共元素,就选择dp[i-1][j]与dp[i][j-1]中最大的
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}

《dp补卡——子序列问题》_算法_02
step3:初始化比较麻烦。

for(int i = 0; i < len1; i++)
{
if( (i >= 1 && dp[i-1][0] == 1 ) || (text1[i] == text2[0]) ) dp[i][0] = 1;
}
for(int j = 0; j < len2; j++)
{
if( (j >= 1 && dp[0][j-1] == 1 ) || (text2[j] == text1[0]) ) dp[0][j] = 1;
}

AC代码:

class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int len1 = text1.size();
int len2 = text2.size();
vector<vector<int>> dp(len1,vector<int>(len2,0));
int maxlen = 0;
//初始化
for(int i = 0; i < len1; i++)
{
if( (i >= 1 && dp[i-1][0] == 1 ) || (text1[i] == text2[0]) ) dp[i][0] = 1;
}
for(int j = 0; j < len2; j++)
{
if( (j >= 1 && dp[0][j-1] == 1 ) || (text2[j] == text1[0]) ) dp[0][j] = 1;
}
//状态转移
for(int i = 1; i < len1; i++)
{
for(int j = 1; j < len2; j++)
{
if(text1[i] == text2[j])
dp[i][j] = dp[i-1][j-1]+1;
else
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
}
return dp[len1-1][len2-1];
}
};

53. 最大子序和

step1:
dp[i],下标<=i的最大和。
step2:

if(dp[i-1] >= 0) dp[i] = dp[i-1] + nums[i];
else dp[i] = nums[i];

step3:
dp[0] = nums[0]
AC代码:

class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int> dp(nums.size(),0);
dp[0] = nums[0];
int maxsum = dp[0];
for(int i = 1; i < nums.size(); i++)
{
if(dp[i-1] >= 0) dp[i] = dp[i-1] + nums[i];
else dp[i] = nums[i];
maxsum = max(maxsum,dp[i]);
}
return maxsum;
}
};

392. 判断子序列

这一题与1143. 最长公共子序列基本是一个题目,不过需要加入一些边界输入判断,如果最长公共子序列长度等于短序列的长度就认为是子序列。
当然更加精准来说,两者的状态转移方程不同。
if (s[i] != t[j]), 说明此时长序列t要删除元素,把t[j]删除,则​​​dp[i][j]​​​则是s[i]与t[j-1]相比较了。
if(s[i] == t[j]),说明此时结果为​​​dp[i][j]+1​​​ 至于为什么if (s[i] != t[j]),不能是从​​dp[i-1][j]​​推导得到,这是因为,​​dp[i-1][j]​​相当于是短序列删除元素s[i]从而与长序列保持一致。而题目中短序列是不能删除元素的。

《dp补卡——子序列问题》_算法_03图1 最长公共子序列


《dp补卡——子序列问题》_i++_04图2 判断子序列


class Solution {
public:
bool isSubsequence(string text1, string text2) {
int len1 = text1.size();
int len2 = text2.size();
if(len1 == 0) return true;
if(len2 < len1) return false;
vector<vector<int>> dp(len1,vector<int>(len2,0));
int maxlen = 0;
//初始化
for(int i = 0; i < len1; i++)
{
if( (i >= 1 && dp[i-1][0] == 1 ) || (text1[i] == text2[0]) ) dp[i][0] = 1;
}
for(int j = 0; j < len2; j++)
{
if( (j >= 1 && dp[0][j-1] == 1 ) || (text2[j] == text1[0]) ) dp[0][j] = 1;
}
//状态转移
for(int i = 1; i < len1; i++)
{
for(int j = 1; j < len2; j++)
{
if(text1[i] == text2[j])
dp[i][j] = dp[i-1][j-1]+1;
else
dp[i][j] = dp[i][j-1];
}
}
return dp[len1-1][len2-1] == len1;
}
};

115. 不同的子序列

step1:​​dp[i][j]​​​:以i为结尾的s序列中出现以j结尾的t子序列的个数。
step2:
分析两种情况:
case1:​​​s[i] 等于 t[j]​​​ 此时​​dp[i][j]​​由两部分组成:
一部分是用s[i]匹配,则​​dp[i][j] = dp[i-1][j-1]​​ 一部分不用s[i]匹配,则​​dp[i][j] = dp[i-1][j]​​ 如: s:bagg 和 t:bag ,s[3] 和 t[2]是相同的,但是字符串s也可以不用s[3]来匹配,即用s[0]s[1]s[2]组成的bag。
所以:

dp[i][j] = dp[i-1][j-1] + dp[i-1][j];

case2:​​s[i] 不等于 t[j]​​​ 此时,​​dp[i][j]​​只能由​​dp[i-1][j]​​推导来
即:​​dp[i][j] = dp[i-1][j]​

step3:
我们已经直到状态由​​​dp[i-1][j] 或者 dp[i-1][j-1]​​​ 所以​​dp[i][0] 和 dp[0][j]​​都一定要初始化。
​dp[i][0]​​:代表以下标i结尾的s序列出现以下标0为结尾的子序列个数。

int times = 0;
for(int i = 0; i < len1; i++)
{
if(s[i] == t[0]) times++;
dp[i][0] = times;
}

​dp[0][j]​​: 代表以下标0结尾的s序列出现以下标j为结尾的子序列个数。

if(s[0] == t[0]) dp[0][0] = 1;

其他情况均为0.

需要注意的地方:由于dp内存的数目过大,需要用long long
,并且对与求和的结果,需要对INT_MAX取模不然会溢出.

class Solution {
public:
int numDistinct(string s, string t) {
int len1 = s.size();
int len2 = t.size();
if(len2 > len1) return 0;
vector<vector<long long>> dp(len1,vector<long long>(len2,0));
//初始化
int times = 0;
for(int i = 0; i < len1; i++)
{
if(s[i] == t[0]) times++;
dp[i][0] = times;
}
//递推
for(int i = 1; i < len1; i++)
{
for(int j = 1; j < len2; j++)
{
if(s[i] == t[j])
dp[i][j] = (dp[i-1][j-1] + dp[i-1][j])%INT_MAX;
else
dp[i][j] = dp[i-1][j];
}
}
return dp[len1-1][len2-1];
}
};

583. 两个字符串的删除操作

这一题,和上一题类似,不过状态转移的时候需要多考虑一点点。
还有就是dp数组定义与上面有点区别,这样对于初始化的时候稍微简便了。
step1:​​​dp[i][j]​​​:以i-1为结尾的字符串word1与以j-1为结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
step2:
分为两个情况:
case1:word1[i-1]与word2[j-1]相同的时候
此时​​​dp[i][j] = dp[i-1][j-1]​​​ case2:word1[i-1]与word2[j-1]相同的时候
此时存在三个子情况:

  • case1:删除word1[i-1]后,有相等机会,此时​​dp[i][j] = dp[i-1][j] + 1​
  • case2:删除word2[j-1]后,有相等机会,此时​​dp[i][j] = dp[i][j-1] + 1​
  • case3:同时删除word1[i-1]与word2[j-1],有相等机会,此时​​dp[i][j] = dp[i-1][j-1] + 2​​ 所以此时有
dp[i][j] = min{dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+2};

step3:
关于初始化,分为​​​dp[i][0]​​​与​​dp[0][j]​​​​dp[i][0]​​:word2为空字符串,以i-1结尾的字符串word1要删除i个元素,才能与word2相同。所以
​dp[i][0] = i​​​​dp[0][j]​​:word1为空字符串,以j-1结尾的字符串word2要删除j个元素,才能与word1相同。所以
​dp[0][j] = j​

AC代码:

class Solution {
public:
int minDistance(string word1, string word2) {
int len1 = word1.size();
int len2 = word2.size();
vector<vector<int>> dp(len1+1,vector<int>(len2+1,0));
//初始化
for(int i = 0; i <= len1; i++)
dp[i][0] = i;
for(int j = 0; j <= len2; j++)
dp[0][j] = j;
for(int i = 1; i <= len1; i++)
{
for(int j = 1; j <= len2; j++)
{
if(word1[i-1] == word2[j-1])
dp[i][j] = dp[i-1][j-1];
else
dp[i][j] =min(dp[i-1][j-1]+2 , min(dp[i-1][j],dp[i][j-1]) + 1);
}
}
return dp[len1][len2];
}
};

72. 编辑距离

step1:​​dp[i][j]​​​表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为​​dp[i][j]​​​ step2:递推公式
仍然分为两种情况:
​word1[i-1] == word2[j-1]​​,此时不需要操作,​​dp[i][j] = dp[i-1][j-1]​​​​word1[i-1] != word2[j-1]​​,此时进行增、删、换

  • case1:word1增加一个元素,使得word1[i-1]与word2[j-1]相同,此时​​dp[i][j] = dp[i-1][j]+1​
  • case2:word1删除一个元素(等同于word2增加一个元素),使得word1[i-1]与word2[j-1]相同,此时​​dp[i][j] = dp[i][j-1]+1​
  • case3:替换元素,word1替换word1[i-1],使得word1[i-1]与word2[j-1]相同,此时​​dp[i][j] = dp[i-1][j-1]+1​​ 所以递推公式如下:
if(word1[i-1] == word2[j-1])
dp[i][j] = dp[i-1][j-1];
else
dp[i][j] = min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1]))+1;

step3:初始化
需要初始化​​​dp[i][0] 和 dp[0][j]​​​​dp[i][0]​​:下标为i-1结尾的字符串word1和空字符串word2的最近距离,此时​​dp[i][0] = i​​ 同理​​dp[0][j] =j​

class Solution {
public:
int minDistance(string word1, string word2) {
int len1 = word1.size();
int len2 = word2.size();
vector<vector<int>> dp(len1+1,vector<int>(len2+1,0));
//初始化
for(int i = 0; i <= len1; i++)
dp[i][0] = i;
for(int j = 0; j <= len2; j++)
dp[0][j] = j;
//开始递推
for(int i = 1; i <= len1; i++)
{
for(int j = 1; j <= len2; j++)
{
if(word1[i-1] == word2[j-1])
dp[i][j] = dp[i-1][j-1];
else
dp[i][j] = min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1])) + 1;
}
}
return dp[len1][len2];
}
};

647. 回文子串 (与 5.最长回文子串思路差不多)

step1:​​dp[i][j]​​​,下标在[i,j]之间的字符串中是否为回文子串
step2:递推公式
if(s[i] == s[j])

  • case1 :下标i==j,说明是同一个字符,为true
  • case2 :下标i与j相差等于1,为true
  • case3 :下标i与j相差大于1,看[i+1,j-1]区间是否为true
if(s[i] == s[j])
{
if( (j-i <= 1) || dp[i+1][j-1] == true )
{
result++;
dp[i][j] = true;
}
}

if(s[i] != s[j]) ​​dp[i][j] = false​​​ step3:初始化
​dp[i][j]​​初始化为false
step4:遍历顺序
在递推公式可以看出,case3是根据​​dp[i+1][j-1]​​推导出来的。所以不能从上到小、从左到右遍历。
从下到上,从左到右遍历,保证​​dp[i+1][j-1]​​在​​dp[i][j]​​之前运算。

for(int i = s.size() - 1; i >= 0; i--)
{
for(int j = i; j < s.size(); j++)
{
}
}

注意,回顾​​dp[i][j]​​​数组定义:下标在[i,j]之间的字符串中是否为回文子串,所以j必定>=i。
AC代码:

class Solution {
public:
int countSubstrings(string s) {
int len = s.size();
vector<vector<bool>> dp(len,vector<bool>(len,false));
int result = 0;
for(int i = len - 1; i >= 0; i--)
{
for(int j = i; j < len; j++)
{
if(s[i] == s[j])
{
if( j - i <= 1 || dp[i+1][j-1] == true)
{
result++;
dp[i][j] = true;
}
}
}
}
return result;
}
};

516. 最长回文子序列

step1:​​dp[i][j]​​​:下标在[i,j]内的最长回文子序列的长度为​​dp[i][j]​​​ step2:
如果​​s[i] == s[j]​​那么​​dp[i][j] = dp[i+1][j-1] + 2;​​ 如果​​s[i] != s[j]​​,说明s[i] 和 s[j]的同时加入并不能增加[i,j]区间的长度,那么分别加入s[i]、s[j]看看哪个可以组成最长回文子序列。
加入s[j]的回文子序列长度为​​dp[i+1][j]​​ 加入s[i]的回文子序列长度为​​dp[i]][j-1]​​​​dp[i][j] = max(dp[i+1][j],dp[i][j-1]);​​ step3:初始化
初始为0。当i==j时,该字符串为单个字符,回文子序列长度为1,即​​dp[i][j] = 1​​ step4:遍历顺序
依靠​​dp[i+1][j-1]、dp[i+1][j]、dp[i][j-1]​​,所以遍历i从下到上。j从左到右。
根据dp数组定义:下标在[i,j]内的最长回文子序列的长度为​​dp[i][j]​​ 所以j>=i。

class Solution {
public:
int longestPalindromeSubseq(string s) {
int len = s.size();
vector<vector<int>> dp(len,vector<int>(len,0));

for(int i = 0; i < len; i++) dp[i][i] = 1;

for(int i = len - 1; i >= 0; i--)
{
for(int j = i + 1; j < len; j++)
{
if(s[i] == s[j]) dp[i][j] = dp[i+1][j-1] + 2;
else dp[i][j] = max(dp[i+1][j],dp[i][j-1]);
}
}
return dp[0][len-1];
}
};


举报

相关推荐

0 条评论