Leetcode记录:序列、字符串DP问题
子序列问题
最长递增子序列
Leetcode 300.给你一个整数数组nums,找到其中最长严格递增子序列的长度。
dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度
int lengthOfLIS(vector<int>& nums) {
if (nums.size() <= 1) return nums.size();
vector<int> dp(nums.size(), 1);
int result = 0;
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > result) result = dp[i]; // 取长的子序列
}
return result;
}
最长连续递增序列
Leetcode 674。 给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
dp[i]:以下标i为结尾的连续递增的子序列长度为dp[i]。
int findLengthOfLCIS(vector<int>& nums) {
if (nums.size() == 0) return 0;
int result = 1;
vector<int> dp(nums.size() ,1);
for (int i = 1; i < nums.size(); i++) {
if (nums[i] > nums[i - 1]) { // 连续记录
dp[i] = dp[i - 1] + 1;
}
if (dp[i] > result) result = dp[i];
}
return result;
}
最长重复子数组
Leetcode 718. 给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。
dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp (nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
int result = 0;
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] = dp[i - 1][j - 1] + 1;
}
if (dp[i][j] > result) result = dp[i][j];
}
}
return result;
}
最长公共子序列
Leetcode 1143. 给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
int longestCommonSubsequence(string text1, string text2) {
vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));
for (int i = 1; i <= text1.size(); i++) {
for (int j = 1; j <= text2.size(); j++) {
if (text1[i - 1] == text2[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[text1.size()][text2.size()];
}
不相交的线
Leetcode 1035. 我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。现在,我们可以绘制一些连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且我们绘制的直线不与任何其他连线(非水平线)相交。以这种方法绘制线条,并返回我们可以绘制的最大连线数。
本质上就是求两个字符串的最长公共子序列的长度,代码一模一样。
最大子序和
Leetcode 53. 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
除了贪心算法之外,用DP也可以解决: dp[i]:包括下标i(以nums[i]为结尾)的最大连续子序列和为dp[i]。
int maxSubArray(vector<int>& nums) {
if (nums.size() == 0) return 0;
vector<int> dp(nums.size());
dp[0] = nums[0];
int result = dp[0];
for (int i = 1; i < nums.size(); i++) {
dp[i] = max(dp[i - 1] + nums[i], nums[i]); // 状态转移公式
if (dp[i] > result) result = dp[i]; // result 保存dp[i]的最大值
}
return result;
}
两字符串关系问题
判断子序列
Leetcode 392. 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
双指针即可解决,也可以用dp: dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。
注意本题如果删元素一定是字符串t,而最长公共子序列是两个字符串都可以删元素。
bool isSubsequence(string s, string t) {
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] = dp[i - 1][j - 1] + 1;
else dp[i][j] = dp[i][j - 1];
}
}
if (dp[s.size()][t.size()] == s.size()) return true;
return false;
}
不同的子序列
Leetcode 115. 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。
dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。 当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成:一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。 当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配(就是模拟在s中删除这个元素),即:dp[i - 1][j]
int numDistinct(string s, string t) {
vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1));
for (int i = 0; i < s.size(); i++) dp[i][0] = 1;
// 注意是从1开始
for (int j = 1; j < t.size(); j++) dp[0][j] = 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] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[s.size()][t.size()];
}
两个字符串的删除操作
Leetcode 583.给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。
dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
int minDistance(string word1, string word2) {
vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1));
for (int i = 0; i <= word1.size(); i++) dp[i][0] = i;
for (int j = 0; j <= word2.size(); j++) dp[0][j] = j;
for (int i = 1; i <= word1.size(); i++) {
for (int j = 1; j <= word2.size(); 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, dp[i][j - 1] + 1);
}
}
}
return dp[word1.size()][word2.size()];
}
编辑距离
Leetcode 72. 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作:插入、删除、替换一个字符。
dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。 注意插入和删除其实本质上是一样的。
int minDistance(string word1, string word2) {
vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1, 0));
for (int i = 0; i <= word1.size(); i++) dp[i][0] = i;
for (int j = 0; j <= word2.size(); j++) dp[0][j] = j;
for (int i = 1; i <= word1.size(); i++) {
for (int j = 1; j <= word2.size(); 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], dp[i - 1][j], dp[i][j - 1]}) + 1;
}
}
}
return dp[word1.size()][word2.size()];
}
回文问题
回文子串
Leetcode 647. 给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
当s[i]与s[j]不相等,那没啥好说的了,dp[i][j]一定是false。 当s[i]与s[j]相等时,这就复杂一些了,有如下三种情况:
情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串。 情况二:下标i 与 j相差为1,例如aa,也是回文子串。 情况三:下标:i 与 j相差大于1的时候,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。
此时唯一要注意的就是遍历顺序,可以列优先,然后从下往上,也可以先从下往上再从左到右。 如果询问最长回文子串的长度的话只需要多加一个长度判断即可完成。
int countSubstrings(string s) {
vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
int result = 0;
for (int i = s.size() - 1; i >= 0; i--) { // 注意遍历顺序
for (int j = i; j < s.size(); j++) {
if (s[i] == s[j]) {
if (j - i <= 1) { // 情况一 和 情况二
result++;
dp[i][j] = true;
} else if (dp[i + 1][j - 1]) { // 情况三
result++;
dp[i][j] = true;
}
}
}
}
return result;
}
最长回文子序列
Leetcode 516. 给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。
dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]。 如果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]看看哪一个可以组成最长的回文子序列。即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
注意要初始化对角线元素:
int longestPalindromeSubseq(string s) {
vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
for (int i = 0; i < s.size(); i++) dp[i][i] = 1;
for (int i = s.size() - 1; i >= 0; i--) {
for (int j = i + 1; j < s.size(); 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][s.size() - 1];
}
正则表达式匹配
Leetcode 10.给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 .
和 *
的正则表达式匹配。.
匹配任意单个字符,*
将前一个元素变为零个或多个
bool isMatch(string s, string p) {
int m = s.size();
int n = p.size();
auto matches = [&](int i, int j){
if(i == 0)
return false;
if(p[j-1] == '.')
return true;
return s[i-1] == p[j-1];
};
vector<vector<int>> dp(m+1, vector<int>(n+1, false));
dp[0][0] = true;
for(int i = 0; i <= m; ++i){
for(int j = 1; j <= n; ++j){
if(p[j-1] == '*'){
dp[i][j] |= dp[i][j-2];
if(matches(i,j-1)) //注意这一步,很巧妙!!
dp[i][j] |= dp[i-1][j];
} else{
if(matches(i, j))
dp[i][j] |= dp[i-1][j-1];
}
}
}
return dp[m][n];
}