目录
题四十 最长公共子序列
题解:
状态方程:dp[i][j]:表示s1字符串[0,i]区间和s2字符串[0,j]区间,最长公共子序列。
根据方程进行推导:分两种情况讨论当s1[i] == s2[j] / s1[i] != s2[j]
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
//1、创建dp表
int n = text1.size();
int m = text2.size();
vector<vector<int>> dp(n+1, vector<int>(m+1));
//2、初始化
text1 = " " + text1;
text2 = " " + text2;
//3、填表
for(int i = 1; i<n+1; ++i)
{
for(int j = 1; j<m+1; ++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[n][m];
}
};
题四十一 不相交的线
题解:
状态方程:dp[i][j]:nums1序列[0,i]区间和nums2序列[0,j]区间,有多少个不相交线。
根据方程进行推导:分两种情况讨论当s1[i] == s2[j] / s1[i] != s2[j]
class Solution {
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
//1、创建dp表
int m = nums1.size();
int n = nums2.size();
vector<vector<int>> dp(m+1, vector<int> (n+1));
//2、初始化
//3、填表
for(int i = 1; i<m+1; ++i)
{
for(int j = 1; j<n+1; ++j)
{
if(nums1[i-1] == nums2[j-1])
{
dp[i][j] = dp[i-1][j-1] + 1;
}
else
{
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
//4、返回值
return dp[m][n];
}
};
题四十二 不同子序列
LCR 097. 不同的子序列 - 力扣(LeetCode)
题解:
dp[i][j]:表示[0,i]区间的s串,有多少个[0,j]区间的t串。
根据此状态方程推导。
class Solution {
public:
int numDistinct(string s, string t) {
//1、创建dp表
int m = s.size();
int n = t. size();
vector<vector<double>> dp(m+1, vector<double> (n+1));
//2、初始化
for(int i = 0; i<m+1; ++i)
{
dp[i][0] = 1;
}
//3、填表
for(int i = 1; i<m+1; ++i)
{
for(int j = 1; j<n+1; ++j)
{
dp[i][j] += dp[i-1][j];
if(s[i-1] == t[j-1])
{
dp[i][j] += dp[i-1][j-1];
}
}
}
//4、返回值
return dp[m][n];
}
};
题四十三 通配符匹配
题解:
?问号可以匹配一个字母
*星号可以匹配任意
s是原字符串,p是带有符号的字符串,问题是带有符号的p串能否匹配原字符串s
dp[i][j]:[0,j]的p串,能否匹配[0,i]的s字串。
根据此状态方程推导,分类情况讨论,分别讨论当d[ j ]是?、*、普通字符时的各种情况。
class Solution {
public:
bool isMatch(string s, string p) {
//1、创建dp表
int m = s.size();
int n = p.size();
vector<vector<bool>> dp(m+1, vector<bool>(n+1));
//2、初始化
s = " " + s;
p = " " + p;
//初始化第一列
//此时p是一个空串,只有s也是空串才能匹配
dp[0][0] = true;
//初始化第一行
//此时s是空串,p只要不是*就是false
for(int j = 1; j<=n; ++j)
{
if(p[j] == '*')
dp[0][j] = true;
else
break;
}
//3、填表
for(int i = 1; i<=m; ++i)
{
for(int j = 1; j<=n; ++j)
{
if(p[j] == '*')
{
dp[i][j] = dp[i-1][j] || dp[i][j-1];
}
else
{
dp[i][j] = (s[i] == p[j] || p[j] == '?') && dp[i-1][j-1];
}
}
}
//4、返回值
return dp[m][n];
}
};
题四十四 正则表达式匹配
题解:
?问号可以匹配一个字母
*星号和前面一个符号,可以匹配任意
s是原字符串,p是带有符号的字符串,问题是带有符号的p串能否匹配原字符串s
dp[i][j]:[0,j]的p串,能否匹配[0,i]的s字串。
分情况讨论:
看p的最后一个位置:
1、当p[j]为*号
1)p[j-1] 为 . 号
2)p[j-1]为普通字母
2、当p[j]为普通字母/ .号
class Solution {
public:
bool isMatch(string s, string p) {
//1、创建dp表
int m = s.size();
int n = p.size();
vector<vector<bool>> dp(m+1, vector<bool>(n+1));
//2、初始化
s = " " + s;
p = " " + p;
//初始化第一列
//此时p是一个空串,只有s也是空串才能匹配
dp[0][0] = true;
//初始化第一行
//此时s是空串,p只要偶数位置为*号,就可以匹配为空
for(int j = 2; j<=n; j += 2)
{
if(p[j] == '*')
dp[0][j] = true;
else
break;
}
//3、填表
for(int i = 1; i<=m; ++i)
{
for(int j = 1; j<=n; ++j)
{
if(p[j] == '*')
{
//p[j-1]是‘.’,匹配空串/匹配多个
dp[i][j] = dp[i][j-2] || (p[j-1] == '.' || p[j-1] == s[i]) && dp[i-1][j];
}
else
{
dp[i][j] = (s[i] == p[j] || p[j] == '.') && dp[i-1][j-1];
}
}
}
//4、返回值
return dp[m][n];
}
};
题四十五 交错字符串
题解:
dp[i][j]:[0,i]的s1,和[0,j]的s2能否匹配s3[0,j+i-1]
s1和s2能够匹配s3:必须满足一个条件,就s3的最后一个元素,要么等于s1的最后一个元素,要么等于s2的最后一个元素
据此分析,分类讨论:
1、s1最后一个位置s1[ i ]匹配s3最后一个位置s3[ j+i-1 ]
2、s2最后一个位置s2[ j ]匹配s3最后一个位置s3[ j+i-1 ]
初始化,多加一行一列。
第一行:s1串为空,s3串只能由s2串匹配,从前往后遍历,一个一个对比,相等就是true。
第一列同理。
自己画图推理。
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
//1、创建dp表
int m = s1.size();
int n = s2.size();
if(m+n < s3.size())
return false;
vector<vector<bool>> dp(m+1, vector<bool> (n+1));
//2、初始化
s1 = " " + s1;
s2 = " " + s2;
s3 = " " + s3;
dp[0][0] = true;
//初始化第一行,s1为空
for(int i = 1; i<=n; ++i)
{
if(s2[i] == s3[i])
dp[0][i] = true;
else
break;
}
//初始化第一列,s2为空
for(int i = 1; i<=m; ++i)
{
if(s1[i] == s3[i])
dp[i][0] = true;
else
break;
}
//3、填表
for(int i = 1; i<=m; ++i)
{
for(int j = 1; j<=n; ++j)
{
dp[i][j] = (dp[i-1][j] && s1[i] == s3[i+j]) ||
(dp[i][j-1] && s2[j] == s3[i+j]);
}
}
//4、返回值
return dp[m][n];
}
};
题四十六 两个字符串的最小ASCII删除和
712. 两个字符串的最小ASCII删除和 - 力扣(LeetCode)
题解:
正难则反,逆向思维:
找所有公共子序列中,ASCII码最大的那个
然后和减去两次该值,即是最小的删除值。
dp[i][j]:s1[0,i]的字串,和s2[0,j]的所有公共子序列中,ACSII码最大的值。
我们现在只研究这两个区间,画图分析。
按照s1和s2的最后一个位置,分情况讨论:
1、s1[i] == s2[j]
2、公共子序列包含s1[i]
3、公共子序列包含s2[j]
4、公共子序列均不包含s1[i],s2[j]
class Solution {
public:
int minimumDeleteSum(string s1, string s2) {
//1、创建dp表
int m = s1.size();
int n = s2.size();
vector<vector<int>> dp(m+1, vector<int> (n+1));
//2、初始化
s1 = " " + s1;
s2 = " " + s2;
//3、填表
for(int i = 1; i<=m; ++i)
{
for(int j = 1; j<=n; ++j)
{
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
if(s1[i] == s2[j])
{
dp[i][j] = max(dp[i][j], dp[i-1][j-1] + s1[i]);
}
}
}
//4、返回值
int sum = 0;
for(int i = 1; i<=m; ++i)
sum += s1[i];
for(int j = 1; j<=n; ++j)
sum += s2[j];
return sum - 2*dp[m][n];
}
};
题四十七 交最长重复子数组
题解:
子数组和子序列的区别:
子数组要求必须连续,不可分割;
而子序列不要求连续。
dp[i][j]:nums1以i为结尾的,和以为j为结尾的nums2两个数组中,公共的 、长度最长的子数组的长度。
根据此状态转移方程推导,画图分析。
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
//1、创建dp表
int m = nums1.size();
int n = nums2.size();
vector<vector<int>> dp(m+1, vector<int> (n+1));
//2、初始化
//3、填表
int ret = 0;
for(int i = 1; i<=m; ++i)
{
for(int j = 1; j<=n; ++j)
{
if(nums1[i-1] == nums2[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
ret = max(ret, dp[i][j]);
}
}
//4、返回值
return ret;
}
};