【动态规划】字符串系列 (题目笔记)

 ​​​​​​​1143. 最长公共子序列

最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:

输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace" ,它的长度为 3 。

示例 2:

输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。

示例 3:

输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。

提示:

  • 1 <= text1.length, text2.length <= 1000
  • text1 和 text2 仅由小写英文字符组成。

1.dp数组含义:

dp[i][j] 代表 text1中 0~ i 的子串 与 text2 中 0~j  的子串 中 最长的公共子序列的长度

2.状态转移方程:

                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]);

                如果text1的第i个字符与text2中第j个字符相等,那么text1中 0~ i 的子 串 与 text2 中 0~j  的子串 中 最长的公共子序列的长度 = text1中 0~ i -1的      子 串 与 text2 中 0~j -1 的子串 中 最长的公共子序列的长度 + 1;

                如果不相等,= text1中 0~ i -1的 子 串 与 text2 中 0~j 的子串 中 最长的公共子序列的长度  与    text1中 0~ i 的 子 串 与 text2 中 0~j 的子串 中 最长的公共子序列的长度 两者之间的最大值。

3.初始化:

        dp[i][0] = dp[0][j] = 0;

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int m = text1.size() + 1;
        int n = text2.size() + 1;
        vector<vector<int>> dp(m,vector<int>(n,0));
        for(int i = 1; i < m; i++){
            for(int j = 1; j < n; 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[m-1][n-1];
    }
};

变:最长公共子串

只需考虑text[i] == text[j] 的 情况即可,代码如下

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int LCSubstr(string& s, string& t){
	int ls = s.size();
	int lt = t.size();
	vector<vector<int>> dp = vector<vector<int>>(ls + 1, vector<int>(lt + 1, 0));
	int maxl = 0;
	for(int i = 1; i <= ls; i++){
		for(int j = 1; j <= lt; j++){
			if(s[i-1] == t[j-1]){
				 dp[i][j] = dp[i-1][j-1] + 1;
			}
			maxl = max(maxl, dp[i][j]);
		}
	}
	return maxl;	
}
int main(){
	string s, t;
	cin >> s >> t;
	cout << LCSubstr(s , t);
	return 0;
}

最长回文子串

5. 最长回文子串

难度中等4836收藏分享切换为英文接收动态反馈

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

示例 2:

输入:s = "cbbd"
输出:"bb"

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成

回文系列的题目,dp数组不出意外的话,dp[i][j]代表的是 下标从i到j的子串是否为回文串

i与j通过长度len(1 ~ n) 相联系

状态转移方程:P(i,j)=P(i+1,j−1)∧(Si​==Sj​)

初始化:子串长度 l 为 1 : d[i][i] = 1;

               l 长度为2:

                        如果s[i] == s[i+1]

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

注意状态转移方程通过子串长度 l 从 3 到 ​​​​​​​len 把i和j联系起来:j = i + l -1;

max 用来记录 最长 回文子串长度,start用来记录该子串开始下标

#include<iostream>
#include<string>
#include<vector>
using namespace std;
string longestPalindrome(string s){
	int len = s.size();
	if(len == 0 || len ==1) return s;
	int start = 0, max = 1;
	vector<vector<int>> dp(len, vector<int>(len));
	for(int i = 0; i < len; i++){
		dp[i][i] = 1;
		if(i < len - 1 && s[i]==s[i+1]){
			dp[i][i+1] = 1;
			max = 2;
			start = i;
		}
	} 
	for(int l = 3; l <= len; l++){
		for(int i = 0; i + l - 1 < len; i++){
			int j = l + i -1;
			if(s[i]==s[j] && dp[i+1][j-1]){
				dp[i][j] = 1;
				start = i;
				max = l;
			}
		}
	}
	return s.substr(start, max);
} 

int main(){
	string s;
	cin >> s;
	cout << longestPalindrome(s);
}


最长公共子序列

516. 最长回文子序列

难度中等739收藏分享切换为英文接收动态反馈

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

示例 1:

输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。

示例 2:

输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。

提示:

  • 1 <= s.length <= 1000
  • s 仅由小写英文字母组成

初始化均和回文子串一样,变动的是状态转移方程:

如果 s[i] = s[j],则首先得到 s 的下标范围 [i+1, j-1] 内的最长回文子序列,然后在该子序列的首尾分别添加 s[i]和 s[j],即可得到 s 的下标范围 [i, j] 内的最长回文子序列,因此

dp[i][j]=dp[i+1][j−1]+2;

如果 s[i] != s[j] ,则 s[i]和 s[j] 不可能同时作为同一个回文子序列的首尾,因此

dp[i][j]=max(dp[i+1][j],dp[i][j−1])。

class Solution {
public:
    int longestPalindromeSubseq(string s){
	int n = s.size();
	vector<vector<int>> f(n, vector<int>(n));
	for(int i = 0; i < n; i++){
		f[i][i] = 1;
		if(i < n-1 && s[i] == s[i+1])
			f[i][i+1] = 2;
		else if(i < n-1 && s[i] != s[i+1])
			f[i][i+1] = 1;	
	}
	for(int len = 3; len <= n; len++){
		for(int i = 0; i + len - 1 < n; i++){
			int j = i + len - 1;
            if(s[i] != s[j]) 
			    f[i][j] = max(f[i][j-1], f[i+1][j]);
			if(s[i] == s[j]) 
				f[i][j] = f[i+1][j-1] + 2;
		}
	}
	return f[0][n-1];
}
};

交错字符串

给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。

两个字符串 s 和 t 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:

s = s1 + s2 + ... + sn
t = t1 + t2 + ... + tm
|n - m| <= 1
交错 是 s1 + t1 + s2 + t2 + s3 + t3 + ... 或者 t1 + s1 + t2 + s2 + t3 + s3 + ...
注意:a + b 意味着字符串 a 和 b 连接。

示例 1:
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
输出:true
示例 2:

输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
输出:false
示例 3:

输入:s1 = "", s2 = "", s3 = ""
输出:true

提示:

0 <= s1.length, s2.length <= 100
0 <= s3.length <= 200
s1、s2、和 s3 都由小写英文字母组成

f[i][j]代表s1的前i个字符,与s2的前j个字符能否交错组成s3的前i+j的子串
s1和s2总长与s3不等,则肯定不能完成交错,返回false 

 

dp[0][0] =  true; 


1.如果s1的第i个字符和s3的第i+j个字符相等,则意味着如果s1的前i-1个字符与s2的前j个字符可以交错组成s3的前i+j-1个字符的话, s1的前i个和s2的前j个是可以交错组成s3的前i+j个的。
则f[i][j] = (f[i-1][j] && s1[i-1] == s3[p]);
对于s2的第j个字符和s3的第i+j个字符相等, s1的前i个字符与s2的前j-1个字符可以交错组成s3的前i+j-1个字符 同理
 f[i][j] |= (f[1][j-1] && s1[i-1] == s3[p]);

 

注意:第一次是= , 第二次是|=,因为可能第一种情况满足,第二种不满足 

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        auto f = vector <vector<int>>(s1.size() + 1, vector <int> (s2.size() + 1, false));

        int n = s1.size(), m = s2.size(), t = s3.size();

        if (n + m != t) {
            return false;
        }

        f[0][0] = true;
        for (int i = 0; i <= n; ++i) {
            for (int j = 0; j <= m; ++j) {
                int p = i + j - 1;
                if (i > 0) {
                    f[i][j] = (f[i - 1][j] && s1[i - 1] == s3[p]);
                }
                if (j > 0) {
                    f[i][j] |= (f[i][j - 1] && s2[j - 1] == s3[p]);
                }
            }
        }

        return f[n][m];
    }
};

不同的子序列

115. 不同的子序列

难度困难693收藏分享切换为英文接收动态反馈

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)

题目数据保证答案符合 32 位带符号整数范围。

示例 1:

输入:s = "rabbbit", t = "rabbit"
输出3
解释:
如下图所示, 有 3 种可以从 s 中得到 "rabbit" 的方案rabbbit
rabbbit
rabbbit

示例 2:

输入:s = "babgbag", t = "bag"
输出5
解释:
如下图所示, 有 5 种可以从 s 中得到 "bag" 的方案babgbag
babgbag
babgbag
babgbag
babgbag提示:
  • 0 <= s.length, t.length <= 1000
  • s 和 t 由英文字母组成

s-----给定串

t------模式串

dp数组:
     dp[i][j]代表 s 前 i 个字符构成的子串,有 模式串 t 前 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];

    意思是,如果当s第i个字符与t的第j个字相等时,我们可以有两种选择方式:把s中的第i个字符,作为与t中第j个字符的匹配记录即承接dp[i - 1][j - 1] ​​​​​​​;或者,不让他与之对应,跳过他,当他不存在,承接dp[i - 1][j]

        不相等时就好办了,想让他俩匹配都没办法匹配,直接承接dp[i - 1][j]

 初始化:

        模式串t为空,只有1种方法;

        给定串s为空,t不为空,有0种方法;

        for (int i = 0; i < s.size(); i++) dp[i][0] = 1;

        for (int j = 1; j < t.size(); j++) dp[0][j] = 0;

class Solution {
public:
    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;
        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()];
    }
};

编辑距离

72. 编辑距离

难度困难2181收藏分享切换为英文接收动态反馈

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数  。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

动态规划:
dp[i][j] 代表 word1 到 i 位置转换成 word2 到 j 位置需要最少步数

所以,

当 word1[i] == word2[j],dp[i][j] = dp[i-1][j-1];

当 word1[i] != word2[j],dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1

其中,dp[i-1][j-1] 表示替换操作,dp[i-1][j] 表示删除操作,dp[i][j-1] 表示插入操作。

注意,针对第一行,第一列要单独考虑,我们引入 '' 下图所示:

第一行,是 word1 为空变成 word2 最少步数,就是插入操作

第一列,是 word2 为空,需要的最少步数,就是删除操作

#include<iostream>
#include<vector>
#include<string>
using namespace std;
int minDistance(string word1, string word2){
	vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1, 0));
	for(int i = 0; i < dp.size(); i++) dp[i][0] = i;
	for(int j = 0; j < dp[0].size(); j++) dp[0][j] = j;
	for(int i = 1; i < dp.size(); i++){
		for(int j = 1; j < dp[i].size(); j++){
			dp[i][j] = min(dp[i-1][j-1], min(dp[i-1][j], dp[i][j-1])) + 1;
			if(word1[i-1] == word2[j-1]){
				dp[i][j] = min(dp[i][j], dp[i-1][j-1]);
			}
		}
	}
   return dp.back().back();		
}

int main(){
	string s1, s2;
	getline(cin, s1);
	getline(cin, s2); 
	cout << minDistance(s1,s2);
	return 0;
	
}

通配符匹配

44. 通配符匹配

难度困难831收藏分享切换为英文接收动态反馈

给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配。

'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。

两个字符串完全匹配才算匹配成功。

说明:

  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *

示例 1:

输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

输入:
s = "aa"
p = "*"
输出: true
解释: '*' 可以匹配任意字符串。

示例 3:

输入:
s = "cb"
p = "?a"
输出: false
解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。

示例 4:

输入:
s = "adceb"
p = "*a*b"
输出: true
解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".

示例 5:

输入:
s = "acdcb"
p = "a*c?b"
输出: false
#include<iostream>
#include<string>
#include<vector>
using namespace std;
bool isMatch(string s, string p) {
    int m = s.size();
    int n = p.size();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1));
    dp[0][0] = true;
    for (int i = 1; i <= n; ++i) {
        if (p[i - 1] == '*') {
           dp[0][i] = true;
        }
        else {
            break;
            }
        }
        // a) *表示空字符,则保持i不动,j-1,如果dp[i][j-1]能匹配,则dp[i][j]能匹配
		// b) *表示多字符,则保持j不动,i-1,如果dp[i-1][j]能匹配,则dp[i][j]能匹配
		//     因为*表示多字符,则当前i-1加上一个字符后的i也就能匹配
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (p[j - 1] == '*') {
                    dp[i][j] = dp[i][j - 1] | dp[i - 1][j];
                }
                else if (p[j - 1] == '?' || s[i - 1] == p[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1];
                }
            }
        }
        return dp[m][n];
    }


int main(){
		string s,p; 
		cin>> s >> p;
		cout << isMatch(s, p) << endl;
	
	

}
 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值