目录
一、简单线性dp
1、最长递增子序列
①、题目描述
②、解题思路
③、代码实现
2、最长连续递增序列
①、题目描述
②、解题思路
③、代码实现
3、最长重复子数组
①、题目描述
②、解题思路
③、代码实现
4、最长公共子序列
①、题目描述
②、解题思路
③、代码实现
5、不相交的线
①、题目描述
②、解题思路
③、代码实现
6、最大子序和
①、题目描述
②、解题思路
③、代码实现
7、判断子序列
①、题目描述
②、解题思路
③、代码实现
二、编辑距离问题
1、不同的子序列
①、题目描述
②、解题思路
③、代码实现
2、两个字符串删除操作
①、题目描述
②、解题思路
③、代码实现
3、最短编辑距离
①、题目描述
②、解题思路
③、代码实现
4、编辑距离
①、题目描述
输入格式
输出格式
数据范围
输入样例:
输出样例:
②、解题思路
③、代码实现
一、简单线性dp
1、最长递增子序列
①、题目描述
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18] 输出:4 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
②、解题思路
题目链接:最长递增子序列
1、确定dp数组含义
dp数组表示以当前字母结尾,范围中的递增子序列的最大长度。
2、递推公式
if(nums[i] > nums[j])
dp[i] = max(dp[i], dp[j] + 1);
3、初始化
所有数组均初始化成1.
4、遍历顺序
双层遍历,从前往后。
③、代码实现
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
int dp[2510];
int res = 0;
for(int i = 0; i < n; i++){
dp[i] = 1;
for(int j = 0; j<=i; j++){
if(nums[i] > nums[j])
dp[i] = max(dp[i], dp[j] + 1);
res = max(res, dp[i]);
}
}
return res;
}
2、最长连续递增序列
①、题目描述
给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
连续递增的子序列 可以由两个下标 l
和 r
(l < r
)确定,如果对于每个 l <= i < r
,都有 nums[i] < nums[i + 1]
,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]]
就是连续递增子序列。
示例 1:
输入:nums = [1,3,5,4,7] 输出:3 解释:最长连续递增序列是 [1,3,5], 长度为3。 尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。
②、解题思路
题目链接:最长连续递增递增
1、确定dp数组含义
dp[]数组表示,以当前位置为结尾时,连续的递增序列的长度。
2、递推公式
if(nums[i] > nums[i - 1]) dp[i] = dp[i - 1] + 1;
else dp[i] = 1;
3、初始化
dp[0] = 1就可以
4、遍历顺序
从前往后。
③、代码实现
int findLengthOfLCIS(vector<int>& nums) {
int dp[10010] = {1};
int n = nums.size();
int res = 1;
for(int i = 1; i < n; i++){
if(nums[i] > nums[i - 1]) dp[i] = dp[i - 1] + 1;
else dp[i] = 1;
res = max(res, dp[i]);
}
return res;
}
3、最长重复子数组
①、题目描述
②、解题思路
题目链接:最长重复子数组
1、确定dp数组含义
dp[i][j]表示第一个数组以 i 结尾,第二个数组以 j 结尾的时候,两个数组中的重复子数组的个数。
2、递推公式
if(nums1[i - 1] == nums2[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
}
res = max(res, dp[i][j]);
3、初始化
全部初始成0
4、遍历顺序
外层第一个数组,内层第二个数组。
③、代码实现
int findLength(vector<int>& nums1, vector<int>& nums2) {
int dp[1010][1010] = {0};
int n = nums1.size(); int m = nums2.size();
int res = 0;
for(int i = 1; i <= n; i++){
for(int j = 1; j <=m; 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;
}
4、最长公共子序列
①、题目描述
给定两个字符串 text1
和 text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0
。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace" 输出:3 解释:最长公共子序列是 "ace" ,它的长度为 3 。
②、解题思路
题目链接:最长公共子序列
1、确定dp数组含义
dp[ i ][ j ]是表示第一个数组i下标结尾,第二个数组j下标结尾的时候,dp[i][j]表示两个的最大公共子序列长度。
2、递推公式
if(text1[i - 1] == text2[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
dp[i][j] = max(max(dp[i - 1][j - 1], dp[i - 1][j]), dp[i][j - 1]);
}
3、初始化
全部初始化成0。
4、遍历顺序
从上到下,从左到右。
③、代码实现
int longestCommonSubsequence(string text1, string text2) {
int dp[1010][1010] = {0};
int n = text1.size(); int m = text2.size();
for(int i = 1; i<=n; i++){
for(int j = 1; j<=m; j++){
if(text1[i - 1] == text2[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
dp[i][j] = max(max(dp[i - 1][j - 1], dp[i - 1][j]), dp[i][j - 1]);
}
}
}
return dp[n][m];
}
5、不相交的线
①、题目描述
在两条独立的水平线上按给定的顺序写下 nums1
和 nums2
中的整数。
现在,可以绘制一些连接两个数字 nums1[i]
和 nums2[j]
的直线,这些直线需要同时满足满足:
-
nums1[i] == nums2[j]
- 且绘制的直线不与任何其他连线(非水平线)相交。
请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。
以这种方法绘制线条,并返回可以绘制的最大连线数。
示例 1:
输入:nums1 = [1,4,2], nums2 = [1,2,4] 输出:2 解释:可以画出两条不交叉的线,如上图所示。 但无法画出第三条不相交的直线,因为从 nums1[1]=4 到 nums2[2]=4 的直线将与从 nums1[2]=2 到 nums2[1]=2 的直线相交。
②、解题思路
题目链接:不相交的线
本题其实就是让我们找到两个数组之间相同顺序的子数组序列,并计算其个数。
1、确定dp数组含义
dp[ i ][ j ] 表示第一个数组第i个位置和第二个数组第j个位置的相同子串的个数。
2、递推公式
if(nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(max(dp[i - 1][j - 1], dp[i - 1][j]), dp[i][j - 1]);
3、初始化
全部初始成0。
4、遍历顺序
先第一个数组,然后第二个数组。
③、代码实现
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size(); int m = nums2.size();
int dp[510][510] = {0};
for(int i = 1; i<=n; i++){
for(int j = 1; j<=m; j++){
if(nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(max(dp[i - 1][j - 1], dp[i - 1][j]), dp[i][j - 1]);
}
}
return dp[n][m];
}
6、最大子序和
①、题目描述
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
②、解题思路
题目链接:最大子数组和
1、确定dp数组含义
dp数组表示当前位置为末尾的话,最长连续数组和最大时多少。
2、递推公式
dp[i] = max(dp[i - 1] + nums[i], nums[i]);
3、初始化
dp[0] = nums[0]。
4、遍历顺序
从前往后遍历。
③、代码实现
int maxSubArray(vector<int>& nums) {
int dp[100010] = {0};
int n = nums.size();
dp[0] = nums[0];
int res = nums[0];
for(int i = 1; i < n; i++){
dp[i] = max(dp[i - 1] + nums[i], nums[i]);
res = max(res, dp[i]);
}
return res;
}
7、判断子序列
①、题目描述
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"
是"abcde"
的一个子序列,而"aec"
不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
致谢:
特别感谢 @pbrother 添加此问题并且创建所有测试用例。
示例 1:
输入:s = "abc", t = "ahbgdc" 输出:true
②、解题思路
题目链接:判断子序列
1、确定dp数组含义
dp[i][j]表示当前第一个数组i下标和第二个数组j下标相同的子数组个数。
2、递推公式
if(s[i - 1] == t[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
dp[i][j] = max(max(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]);
}
3、初始化
全部初始成0。
4、遍历顺序
从小的数组开始遍历,外层小的,内层大的。
③、代码实现
int dp[110][10010];
bool isSubsequence(string s, string t) {
if(s.size() == 0) return true;
int n = s.size(); int m = t.size();
for(int i = 1; i<=n; i++){
for(int j = 1; j<=m; j++){
if(s[i - 1] == t[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
dp[i][j] = max(max(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]);
}
}
}
return dp[n][m] == n;
}
二、编辑距离问题
1、不同的子序列
①、题目描述
给你两个字符串 s
和 t
,统计并返回在 s
的 子序列 中 t
出现的个数,结果需要对 109 + 7 取模。
示例 1:
输入:s = "rabbbit", t = "rabbit"输出
:3
解释: 如下所示, 有 3 种可以从 s 中得到"rabbit" 的方案
。rabbbit
rabbbit
rabbbit
②、解题思路
题目链接:不同子序列
1、确定dp数组含义
dp[i][j]表示两个数组分别以i结尾和以j结尾的时候,相同的第二个数组在一中有几个字串。
2、递推公式
if(s[i - 1] == t[j - 1]){
dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j]) % N;
}
else dp[i][j] = dp[i - 1][j];
3、初始化
for(int i = 0; i<=n; i++) dp[i][0] = 1;
将最左边一列初始成1,最上面初始成0.这因为不相等是从上面推出来的, 左面是相加的,相加肯定得有值,不然就都是0了,上面是不相等的时候继承,因此可以是0。
4、遍历顺序
从上到下,从左到右,大的字符串在外层,小的字符串在内层。
③、代码实现
typedef long long LL;
LL N = 1e9 + 7;
int numDistinct(string s, string t) {
int n = s.size(); int m = t.size();
int dp[1010][1010] = {0};
for(int i = 0; i<=n; i++) dp[i][0] = 1;
for(int i = 1; i<=n; i++){
for(int j = 1; j<=m; j++){
if(s[i - 1] == t[j - 1]){
dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j]) % N;
}
else dp[i][j] = dp[i - 1][j];
}
}
return dp[n][m];
}
2、两个字符串删除操作
①、题目描述
给定两个单词 word1
和 word2
,返回使得 word1
和 word2
相同所需的最小步数。
每步 可以删除任意一个字符串中的一个字符。
示例 1:
输入: word1 = "sea", word2 = "eat" 输出: 2 解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat "变为 "ea"
②、解题思路
题目链接:两个字符串删除操作
1、确定dp数组含义
dp[i][j]表示分别以i和j相同结尾的两个字符串想完全相同需要进行的删除操作数。
2、递推公式
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);
}
3、初始化
dp[i][0]每个下标初始成i。
dp[0][j]每个下标初始成j。
4、遍历顺序
两个数组分两层遍历。左到右,上到下遍历。
③、代码实现
int minDistance(string word1, string word2) {
int n = word1.size(); int m = word2.size();
int dp[510][510] = {0};
for(int i = 1; i<=n; i++) dp[i][0] = i;
for(int j = 1; j<=m; j++) dp[0][j] = j;
for(int i = 1; i<=n; i++){
for(int j = 1; j<=m; 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[n][m];
}
3、最短编辑距离
①、题目描述
给你两个单词 word1
和 word2
, 请返回将 word1
转换成 word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例 1:
输入:word1 = "horse", word2 = "ros" 输出:3 解释: horse -> rorse (将 'h' 替换为 'r') rorse -> rose (删除 'r') rose -> ros (删除 'e')
②、解题思路
题目链接:编辑距离
1、确定dp数组含义
dp[i][j]表示w1i下标和w2下标数组完全相同的话需要的修改次数。
2、递推公式
if(word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
else {
dp[i][j] = min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}) + 1;
3、初始化
for(int i = 0; i<=n; i++) dp[i][0] = i;
for(int j = 0; j<=m; j++) dp[0][j] = j;
4、遍历顺序
从上到下,从左到右。
③、代码实现
int minDistance(string word1, string word2) {
int n = word1.size(); int m = word2.size();
for(int i = 0; i<=n; i++) dp[i][0] = i;
for(int j = 0; j<=m; j++) dp[0][j] = j;
for(int i = 1; i <= n; i++){
for(int j = 1; j<=m; 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], dp[i][j - 1], dp[i - 1][j - 1]}) + 1;
}
}
}
return dp[n][m];
}
4、编辑距离
①、题目描述
给定 n 个长度不超过 1010 的字符串以及 m 次询问,每次询问给出一个字符串和一个操作次数上限。
对于每次询问,请你求出给定的 n个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。
每个对字符串进行的单个字符的插入、删除或替换算作一次操作。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含一个字符串,表示给定的字符串。
再接下来 m 行,每行包含一个字符串和一个整数,表示一次询问。
字符串中只包含小写字母,且长度均不超过 1010。
输出格式
输出共 m 行,每行输出一个整数作为结果,表示一次询问中满足条件的字符串个数。
数据范围
1≤n,m≤10001≤
输入样例:
3 2
abc
acd
bcd
ab 1
acbd 2
输出样例:
1
3
②、解题思路
题目链接:编辑距离
1、确定dp数组含义
dp[i][j]表示w1i下标和w2下标数组完全相同的话需要的最少修改次数。
2、递推公式
if(a[i - 1] == b[j - 1])dp[i][j] = dp[i - 1][j - 1];
else {
dp[i][j] = min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}) + 1;
3、初始化
for(int i = 1; i <= a.size(); i++) dp[i][0] = i;
for(int i = 1; i <= b.size(); i++) dp[0][i] = i;
4、遍历顺序
从左到右,从上到下。
③、代码实现
#include<iostream>
using namespace std;
#include<string>
#include<vector>
#include<algorithm>
int n, m;
vector<string> strs;
const int N = 1010;
int dp[N][N];
int get_num(string a, int n)
{
int res = 0;
for(int k = 0; k < strs.size(); k++)
{
string b = strs[k];
for(int i = 1; i <= a.size(); i++) dp[i][0] = i;
for(int i = 1; i <= b.size(); i++) dp[0][i] = i;
for(int i = 1; i<=a.size(); i++) {
for(int j = 1;j <= b.size(); j++){
if(a[i - 1] == b[j - 1])dp[i][j] = dp[i - 1][j - 1];
else {
dp[i][j] = min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}) + 1;
}
}
}
if(dp[a.size()][b.size()] <= n) res ++;
}
return res;
}
int main()
{
cin>>n>>m;
while(n -- ){
string a; cin>>a;
strs.push_back(a);
}
while(m -- )
{
string s; int n;
cin>>s>>n;
cout<<get_num(s, n)<<endl;
}
}