1、最长公共子序列
1143. 最长公共子序列 - 力扣(LeetCode)
class Solution
{//1、状态表示:// 经验 + 题目要求// (1)选取第一个字符串[0,i]区间,以及第二个字符串[0,j]区间,作为研究对象// (2)根据题目要求,确定状态表示// dp[i][j]:s1的[0,i]区间和s2的[0,j]区间中所有的子序列中,最长公共子序列的长度//2、状态转移方程:// 根据最后一个位置的状况,分情况讨论//(1)如果s1[i] == s2[j] == x,那么可以确定,最长的公共子序列一定是以x结尾的//所以dp[i][j] = dp[i-1][j-1] + 1//(2)如果s[i] != s2[j]//那么可以去s1[0,i-1]和s2[0,j],或者s1[0,i]和s2[0,j-1],或者s1[0,i-1]和s2[0,j-1]去找//第一种和第二种都是包含第三种的,但是不干扰,因为求的是max,所以这里可以不要第三种情况//3、初始化://关于字符串的dp问题:有时空串是有研究意义的//引入空串,方便初始化//可以引入一行一列,然后注意下标映射(可以在前面加一个" ",来对应下标),并且此时不会越界//第一行,第一列为0即可。因为空串的长度为0//4、顺序:根据状态转移方程:从上往下,从左往右//return dp[n1][n2];
public:int longestCommonSubsequence(string s1, string s2) {int n1 = s1.size(), n2 = s2.size();s1 = ' ' + s1, s2 = ' ' + s2;vector<vector<int>> dp(n1+1,vector<int>(n2+1));for(int i = 1;i<=n1;i++)for(int j = 1;j<=n2;j++)if(s1[i] == s2[j]) dp[i][j] = dp[i-1][j-1] + 1;elsedp[i][j] = max(dp[i-1][j] , dp[i][j-1]);return dp[n1][n2];}
};
2、不相交的线
1035. 不相交的线 - 力扣(LeetCode)
class Solution
{//分析:要连线不相交//如果我从两个数组中选出公共子序列的话,那连线肯定不相交//所以本题转换为最长公共子序列的问题//dp[i][j]:从nums1[0,i]和nums2[0,j]中选,最长公共子序列的长度
public:int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {int n1 = nums1.size(), n2 = nums2.size();vector<vector<int>> dp(n1+1,vector<int>(n2+1));for(int i = 1;i<=n1;i++)for(int j = 1;j<=n2;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]);return dp[n1][n2];}
};
3、不同的子序列
115. 不同的子序列 - 力扣(LeetCode)
class Solution
{//题目解析:在s的子序列中找t出现的个数//那么可以将t看作公共子序列//1、状态表示//dp[i][j]:在s的区间[0,j]中的子序列中,出现t[0,i]的个数//2、状态转移方程//根据s的子序列的最后一个位置,包不包含s[j]//(1)包含,首先必须s[j] == t[i]--->dp[i][j] = dp[i-1][j-1]//(2)不包含,dp[i][j] = dp[i][j-1]//dp[i][j] = dp[i-1][j-1] + dp[i][j-1]//3、初始化://引入空串//里面的值要保证后续的填表是正确的--->不会越界,填表正确//下标的映射关系--->可以str = ' ' + str //dp[0][j] = 1;//顺序:dp[i][j]需要左上,和左边//从上往下,从左往右//return dp[t.size()][s.size()]
public:int numDistinct(string s, string t) {int m = t.size(), n = s.size();const int MOD = 1e9 + 7;vector<vector<int>> dp(m+1,vector<int>(n+1));for(int j = 0;j<=n;j++) dp[0][j] = 1;for(int i = 1;i<=m;i++)for(int j = 1;j<=n;j++){dp[i][j] = dp[i][j-1];//不包含if(s[j-1] == t[i-1]) dp[i][j] += dp[i-1][j-1];//包含dp[i][j] %= MOD;}return dp[m][n];}
};
4、通配符匹配
44. 通配符匹配 - 力扣(LeetCode)
class Solution
{//1、状态表示//dp[i][j]:s[0,i]区间内的子串能否被,p[0,j]区间内的子串匹配//2、状态转移方程://经验:根据最后一个位置的状况,分情况讨论//1):p[j] == 普通字符--->那么p[j]至少需要等于s[i],然后需要p[0,j-1]和s[0,i-1]匹配,则dp[i][j] = s[i] == p[j] && dp[i-1][j-1]//2):p[j] == ? --->?必须匹配最后一个字符,则dp[i][j] = dp[i-1][j-1]//3):p[j] == * --->:// a) * 匹配空字符,dp[i][j] = dp[i][j-1]// b) * 匹配一个字符,dp[i][j] = dp[i-1][j-1]// c) * 匹配两个字符,dp[i][j] = dp[i-2][j-1]// ....// n) * 匹配n个字符,dp[i][j] = dp[i-n][j-1],0 <= n <= i//如果这样做的话,时间复杂度会到n三次方//考虑优化(前提p[j] == *)://方法一:数学方法----等价替换// dp[i][j] = dp[i][j-1] || dp[i-1][j-1] || dp[i-2][j-1] ......// 则dp[i-1][j] = dp[i-1][j-1] || dp[i-2][j-1] || dp[i-3][j-1] .....// 所以将dp[i-1][j]代入dp[i][j]中// dp[i][j] = dp[i][j-1] || dp[i-1][j];//方法二:根据状态表示 以及 实际情况,优化状态转移方程// p[j] == *,匹配空串,dp[i][j] = dp[i][j-1]// p[j] == *.匹配一个,但是!不把 * 丢掉,则dp[i][j] = dp[i-1][j],因为如果下一个匹配,不需要*,那么*可以匹配空串,不影响// 可以理解为*传递下去// 则dp[i][j] = dp[i][j-1] || dp[i-1][j]//最终----->dp[i][j] = dp[i][j-1] || dp[i-1][j]//3、初始化://引入空串--->多加一行一列//里面的值,要保证后续的填表是正确的//注意下标映射关系,可以str = ' ' + str//dp[0][0] = true;//对于dp[0][j],如果p[0,x]全是*,那么dp[0][0 ~ x]全是true,如果p[x+1]不为*,那么dp[0][x+1 ~ p.size]全为false//4、填表顺序:看状态转移方程//从上往下,从左往右//5、return dp[s.size][p.size]
public:bool isMatch(string s, string p) {int n = s.size(), m = p.size();vector<vector<bool>> dp(n+1,vector<bool>(m+1,false));//初始化dp[0][0] = true;for(int j = 1;j<=m;j++)if(p[j-1] == '*') dp[0][j] = true;else break;for(int i = 1;i<=n;i++)for(int j = 1;j<=m;j++){if(p[j-1] >= 'a' && p[j-1] <= 'z') dp[i][j] = (s[i-1] == p[j-1] && dp[i-1][j-1]);else if(p[j-1] == '?') dp[i][j] = dp[i-1][j-1];else dp[i][j] = dp[i][j-1] || dp[i-1][j];}return dp[n][m];}
};
5、正则表达式匹配
10. 正则表达式匹配 - 力扣(LeetCode)
class Solution
{//题目解析://a* = 空串 || a || aa || aaa ......// .* = 空串 || . || .. || ... || .........//1、状态表示://经验 + 题目要求//dp[i][j]:s这个字符串[0,i]区间的子串,是否能被p字符串[0,j]区间的子串匹配//2、状态转移方程:// 根据最后一个位置的状态,分情况讨论//(1):如果p[j] == [a,z],if(p[j] == s[i]),dp[i][j] = dp[i-1][j-1],即dp[i][j] = p[j] == s[i] && dp[i-1][j-1]//(2):如果p[j] == '.',p[j]匹配s[i],dp[i][j] = dp[i-1][j-1]//(3):如果p[j] = '*'// 1):如果p[j-1] == '.':// a)翻译成--空串,dp[i][j] = dp[i][j-2]// b)翻译成--1个点,dp[i][j] = dp[i-1][j-2]// c)翻译成--2个点,dp[i][j] = dp[i-2][j-2]// .......// n)翻译成--n个点,dp[i][j] = dp[i-n][j-2]// dp[i][j] = dp[i][j-2] || dp[i-1][j-2] || dp[i-2][j-2] || .....// 如果暴力遍历,时间会到n三方,所以需要优化// 优化方法一:数学方法:// dp[i][j] = dp[i][j-2] || dp[i-1][j-2] || dp[i-2][j-2] || .....// 则:dp[i-1][j] = dp[i-1][j-2] || dp[i-2][j-2] || dp[i-3][j-2] || ....// 将dp[i-1][j]代入dp[i][j]// --------->则dp[i][j] = dp[i][j-2] || dp[i-1][j]// 优化方法二:状态表示结合实际情况,优化状态转移方程// .*去匹配s[i]后,不把".*"丢掉,让它继续去匹配s[i-1],s[i-2]....// 这里是假想,没有丢掉".*",因为".*"可以匹配很多个// 那么dp[i][j] = dp[i-1][j]// .*什么都不匹配,直接丢掉,则dp[i][j] = dp[i][j-2]// --------->则dp[i][j] = dp[i][j-2] || dp[i-1][j]// 2):p[j-1] == [a,z] == #,(假设这个字母为#)// 直接优化:// a)#*去匹配一个空串,则dp[i][j] = dp[i][j-2]// b)#*去匹配一个,然后保留,但是需要保证p[j-1] == s[i],然后留着去匹配s[i-1]// 则dp[i][j] = p[j-1] == s[i] && dp[i-1][j]// -------->则dp[i][j] = dp[i][j-2] || (p[j-1] == s[i] && dp[i-1][j])// 总结:(1)p[j] == s[i],dp[i][j] = dp[i-1][j-1]// (2)p[j] == '.',dp[i][j] = dp[i-1][j-1]// 合并:if(p[j] == s[i] || p[j] == '.') dp[i][j] = dp[i-1][j-1]// (3)p[j] == *// dp[i][j] = dp[i][j-2] || ((p[j-1] == s[i] || p[j-1] == '.') && dp[i-1][j])//3、初始化://引入空串--->多加一行一列//此时填表不会越界//里面的值要保证后续的填表是正确的//注意下标的映射关系//dp[0][0] = true;//dp[i][0] = false;//dp[0][j],如果出现"_*",可以匹配空串,"_*_*_*"也可以,"_*_*__"不可以// 所以,当偶数位置出现连续的*,里面是true,否则之后全是false//4、顺序:由状态转移方程//从上往下,从左往右//5、return dp[s.size()][p.size()]
public:bool isMatch(string s, string p) {int n = s.size(), m = p.size();s = ' ' + s, p = ' ' + p;vector<vector<bool>> dp(n+1,vector<bool>(m+1,false));dp[0][0] = true;for(int j = 2;j<=m;j+=2)if(p[j] == '*') dp[0][j] = true;else break;for(int i = 1; i<= n ;i++)for(int j = 1;j<=m;j++)if(p[j] == '*')dp[i][j] = dp[i][j-2] || ((p[j-1] == s[i] || p[j-1] == '.') && dp[i-1][j]);elsedp[i][j] = (p[j] == s[i] || p[j] == '.') && dp[i-1][j-1];return dp[n][m];}
};
6、交错字符串
97. 交错字符串 - 力扣(LeetCode)
class Solution
{//1、状态表示://预处理 str = ' ' + str//dp[i][j]:s1[1,i]区间内和s2[1,j]区间内的所有分割子串,能否交错拼接s3[1,i+j]区间字符串//2、状态转移方程:// 根据最后一个位置的状态,分情况讨论://(1) 如果s1[i] != s3[i+j] && s2[j] != s3[i+1],不能拼接,false//(2) 如果s1[i] == s3[i+j],dp[i][j] = dp[i-1][j],即s1[1,i-1],s2[1,j]拼接s3[1,i+j-1]//(3) 如果s2[j] == s3[i+j],dp[i][j] = dp[i][j-1]//(4) 如果 s1[i] == s3[i+j] && s2[j] == s3[i+j],不用管,因为(2)(3)就是(4)的组成//3、初始化://已经预处理过了,多开一行一列//dp[0][0] = true;//dp[0][j]:如果s2和s3对应位置相同,true,只要出现一个不同,那就后面全是false//dp[i][0]:和dp[0][j]同理初始化//4、顺序:从上往下,从左往右//5、return dp[s1.size()][s2.size()]public:bool isInterleave(string s1, string s2, string s3) {int n1 = s1.size(), n2 = s2.size(), n3 = s3.size();if(n1 + n2 != n3) return false;//处理边界情况s1 = ' ' + s1, s2 = ' ' + s2, s3 = ' ' + s3;vector<vector<bool>> dp(n1+1,vector<bool>(n2+1));dp[0][0] = true;for(int j = 1;j<=n2;j++)if(s2[j] == s3[j]) dp[0][j] = true;else break;for(int i = 1;i<=n1;i++)if(s1[i] == s3[i]) dp[i][0] = true;else break;for(int i = 1;i<=n1;i++)for(int j = 1;j<=n2;j++)dp[i][j] = (s1[i] == s3[i+j] && dp[i-1][j]) || (s2[j] == s3[i+j] && dp[i][j-1]);return dp[n1][n2];}
};
7、两个字符串的最小ASCII删除和
712. 两个字符串的最小ASCII删除和 - 力扣(LeetCode)
class Solution
{//题目解析:找删除的ASCII和最小,那么可以转换为删除后的字符串的ASCII和最大//即:求两个字符串里面,所有的公共子序列里面,ASCII和的最大值---->正难则反//1、状态表示//经验 + 题目要求//dp[i][j]:s1[0,i]和s2[0,j]区间中,所有公共子序列里面,ASCII和的最大值//2、状态转移方程// 根据最后一个字符的状态,来分析状态转移方程//(1) 选了s1[i]和s2[j]-->首先s1[i] == s2[j],dp[i][j] = dp[i-1][j-1] + s1[i](s2[j])//(2) 选了s1[i],没选s2[j]-->dp[i][j-1],这里并不是等于,因为这里dp[i][j-1],这个s[i]也是有选和不选的情况,那么dp[i][j-1]是包含dp[i-1][j-1]的,即(2)包含(4)//(3) 没选s1[i],选了s2[j]-->dp[i-1][j],同理(3)是包含(4)的//(4) s1[i]和s2[j]都没选-->dp[i-1][j-1],(4)是可以不用计算的//3、初始化://引入空串,多加一行一列//dp[0][0] = 0;//dp[0][j] = 0,dp[i][0] = 0;//注意下标映射//4、顺序:从上往下,从左往右//5、找到 dp[s1.size()][s2.size()]//统计s1 + s2的ASCII和sum//return sum - 2*dp[s1.size()][s2.size()]public:int minimumDeleteSum(string s1, string s2) {int n1 = s1.size(), n2 = s2.size();vector<vector<int>> dp(n1+1,vector<int>(n2+1));for(int i = 1;i<=n1;i++)for(int j = 1;j<=n2;j++){dp[i][j] = max(dp[i][j-1],dp[i-1][j]);if(s1[i-1] == s2[j-1])dp[i][j] = max(dp[i][j],dp[i-1][j-1] + s1[i-1]);}int sum = 0;for(auto x : s1) sum += x;for(auto x : s2) sum += x;return sum - 2 * dp[n1][n2];}
};
8、最长重复子数组
718. 最长重复子数组 - 力扣(LeetCode)
class Solution
{//1、状态表示://dp[i][j]:nums1[i]为结尾和nums2[j]为结尾的所有重复子数组中,最长的重复子数组的长度//2、状态转移方程://(1)如果nums1[i] != nums2[j],dp[i][j] = 0;//(2)如果nums1[i] == nums2[j],dp[i][j] = dp[i-1][j-1] + 1;//3、初始化://为了方便填值,多加一行一列//初始化为0//4、顺序:从上往下//5、return dp中的max
public:int findLength(vector<int>& nums1, vector<int>& nums2) {int n1 = nums1.size(), n2 = nums2.size();vector<vector<int>> dp(n1+1,vector<int>(n2+1));int res = INT_MIN;for(int i = 1;i<=n1;i++)for(int j = 1;j<=n2;j++){if(nums1[i-1] == nums2[j-1]) dp[i][j] = dp[i-1][j-1] + 1;res = max(res,dp[i][j]);}return res; }
};