0
点赞
收藏
分享

微信扫一扫

LeetCode动态规划基础题-子字符串问题(13题)


前言

觉得有帮助的,要不三连一下~

LeetCode动态规划基础题-子字符串问题(13题)_动态规划

既然不能出去玩,那还是来学习吧。

LeetCode动态规划基础题-子字符串问题(13题)_leetcode_02

一、子字符串问题

LeetCode动态规划基础题-子字符串问题(13题)_子序列_03

1 最长递增子序列

300.最长递增子序列


​​力扣题目链接(opens new window)​​

给你一个整数数组 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 。

示例 2: 输入:nums = [0,1,0,3,2,3] 输出:4

示例 3: 输入:nums = [7,7,7,7,7,7,7] 输出:1

提示:

  • 1 <= nums.length <= 2500
  • -10^4 <= nums[i] <= 104


  1. dp数组的定义

dp[i]表示i之前包括i的以nums[i]结尾最长上升子序列的长度

  1. 状态转移方程

位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。

if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
  1. 初始化
  2. 遍历顺序
  3. 距离说明

LeetCode动态规划基础题-子字符串问题(13题)_动态规划_04

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

2 最长连续递增序列

  1. 最长连续递增序列


给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个下标 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 隔开。

示例 2: 输入:nums = [2,2,2,2,2] 输出:1 解释:最长连续递增序列是 [2], 长度为1。

提示:

  • 0 <= nums.length <= 10^4
  • -10^9 <= nums[i] <= 10^9


与上一题最大的区别就是连续二字

  1. 确定dp数组(dp table)以及下标的含义

dp[i]:以下标i为结尾的数组的连续递增的子序列长度为dp[i]

  1. 确定递推公式

如果 nums[i + 1] > nums[i],那么以 i+1 为结尾的数组的连续递增的子序列长度 一定等于 以i为结尾的数组的连续递增的子序列长度 + 1 。

for (int i = 0; i < nums.size() - 1; i++) {
if (nums[i + 1] > nums[i]) { // 连续记录
dp[i + 1] = dp[i] + 1; // 递推公式
}
}
  1. 初始化
  2. 遍历舒徐
  3. 举例分析

LeetCode动态规划基础题-子字符串问题(13题)_子序列_05

class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
int n = nums.size();
if(n<=1) return n;
vector<int> dp(n,1);
int res = 0;
for(int i = 1; i < n; ++i){
if(nums[i] > nums[i-1]){
dp[i] = dp[i-1] + 1;
}
//cout<<dp[i]<<" ";
res = max(res, dp[i]);
}
return res;
}
};

3 最长重复子数组

  1. 最长重复子数组


给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例:

输入: A: [1,2,3,2,1] B: [3,2,1,4,7] 输出:3 解释: 长度最长的公共子数组是 [3, 2, 1] 。

提示:

  • 1 <= len(A), len(B) <= 1000
  • 0 <= A[i], B[i] < 100


这道题基本上就是上课讲动态规划的经典例题了。

LeetCode动态规划基础题-子字符串问题(13题)_动态规划_06

  1. 确定dp

以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为​​dp[i][j]​​。

  1. 确定递推公式

​dp[i][j] = dp[i - 1][j - 1] + 1;​

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] + 1;
}
if (dp[i][j] > result) result = dp[i][j];
}
}
  1. dp数组如何初始化

​dp[1][1] = dp[0][0] + 1​​​,只有​​dp[0][0]​​初始为0

  1. 遍历顺序
  2. 举例推导
class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
vector<vector<int>> dp (A.size() + 1, vector<int>(B.size() + 1, 0));
int result = 0;
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] + 1;
}
if (dp[i][j] > result) result = dp[i][j];
}
}
return result;
}
};

滚动数组

LeetCode动态规划基础题-子字符串问题(13题)_动态规划_07

我们可以看出dp[i][j]都是由dp[i - 1][j - 1]推出。那么压缩为一维数组,也就是dp[j]都是由dp[j - 1]推出。

也就是相当于可以把上一层dp[i - 1][j]拷贝到下一层dp[i][j]来继续用。

此时遍历B数组的时候,就要从后向前遍历,这样避免重复覆盖

class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
vector<int> dp(vector<int>(B.size() + 1, 0));
int result = 0;
for (int i = 1; i <= A.size(); i++) {
for (int j = B.size(); j > 0; j--) {
if (A[i - 1] == B[j - 1]) {
dp[j] = dp[j - 1] + 1;
} else dp[j] = 0; // 注意这里不相等的时候要有赋0的操作
if (dp[j] > result) result = dp[j];
}
}
return result;
}
};


  • 时间复杂度:LeetCode动态规划基础题-子字符串问题(13题)_算法_08,n 为A长度,m为B长度
  • 空间复杂度:LeetCode动态规划基础题-子字符串问题(13题)_leetcode_09


4 最长公共子序列

1143.最长公共子序列


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

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

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

示例 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 <= 1000
  • 1 <= text2.length <= 1000 输入的字符串只含有小写英文字符。


举个栗子分析就能发现dp状态转移方程

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

5 不相交的线

1035.不相交的线

我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。

现在,我们可以绘制一些连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且我们绘制的直线不与任何其他连线(非水平线)相交。

以这种方法绘制线条,并返回我们可以绘制的最大连线数。

LeetCode动态规划基础题-子字符串问题(13题)_leetcode_10


输入:nums1 = [1,4,2], nums2 = [1,2,4]
输出:2
解释:可以画出两条不交叉的线,如上图所示。
但无法画出第三条不相交的直线,因为从 nums1[1]=4 到 nums2[2]=4 的直线将与从 nums1[2]=2 到 nums2[1]=2 的直线相交。


这道题 有两个关键的地方

不能改变顺序

不可相交

class Solution {
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
// 这道题其实就是最长公共子序列的变形
//dp[i][j] 表示以i-1为结尾的nums1与j-1为结尾的nums2的最长公共字符串
int n = nums1.size();
int m = nums2.size();
if(n==0 || m == 0) return 0;
vector<vector<int>> dp(n+1, vector<int>(m+1,0));
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;
}else{
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
res = max(dp[i][j], res);
}
}
return res;
}
};

6 最大子数组和

  1. 最大子数组和

关键点在于定义dp

​dp[i]​​ 表示加入nums[i]后连续数组的最大和

dp[i] = max(dp[i-1] + nums[i], nums[i]);
maxSum = max(dp[i],maxSum);
class Solution {
public:
int maxSubArray(vector<int>& nums) {
// 动态规划
int maxSum = INT_MIN;
int n = nums.size();
vector<int>dp(n);
dp[0] = nums[0];
maxSum = nums[0];
for(int i = 1; i < nums.size(); i++){
dp[i] = max(dp[i-1] + nums[i], nums[i]);
maxSum = max(dp[i],maxSum);
}
return maxSum;
}
};

7 判断子序列

​​392. 判断子序列​​

class Solution {
public:
bool isSubsequence(string s, string t) {
int n = s.size();
int m = t.size();
vector<vector<int>>dp(n+1, vector<int>(m+1,0));
for(int i = 1; i <=n; ++i){
for(int j = 1; j<=m; ++j){
if(s[i-1] == t[j-1]){
// 相等的长度+1
// dp[i-1][j-1]
dp[i][j] = dp[i-1][j-1] + 1;
}else{
// 如果不相等,需要删除
// 则看的是s[i-1]与t[j-2]的结果
// dp[j-1]
dp[i][j] = dp[i][j-1];
}
}
}
if(dp[n][m] == n) return true;
else return false;
}
};

8 编辑距离

​​72. 编辑距离​​

class Solution {
public:
int minDistance(string word1, string word2) {
// 动态规划
// dp[i][j]表示i-1为结尾的word1字符串与j-1结尾的字符串的最小编辑距离
int n = word1.size();
int m = word2.size();
vector<vector<int>> dp(n+1,vector<int>(m+1,0));
// 初始化
// dp[i][0]相当于i结尾的字符串与空字符串的编辑距离
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][j-1], dp[i-1][j], dp[i-1][j-1]}) + 1;
}
}
}
return dp[n][m];
}
};

9 两个字符串的删除操作

​​583. 两个字符串的删除操作​​

class Solution {
public:
int minDistance(string word1, string word2) {
// dp[i][j] 以i-1的word1字符串与以j-1的word2字符串的相同的最小步数
int n = word1.size();
int m = word2.size();
vector<vector<int>> dp(n+1, vector<int>(m+1));
// 初始化
for(int i = 0; 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{
// 有三种情况
// word1 删除一个,word2删除一个 word1,word2删除
dp[i][j] = min({dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 2});
}
}
}
return dp[n][m];
}
};

10 编辑距离问题小结:

判断子序列

​​动态规划:392.判断子序列 (opens new window)​​给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

dp[i][j]表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]

这道题目 其实是可以用双指针或者贪心的的,但是我在开篇的时候就说了这是编辑距离的入门题目,因为从题意中我们也可以发现,只需要计算删除的情况,不用考虑增加和替换的情况。

  • if (s[i - 1] == t[j - 1])
  • t中找到了一个字符在s中也出现了
  • if (s[i - 1] != t[j - 1])
  • 相当于t要删除元素,继续匹配

状态转移方程:

if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = dp[i][j - 1];

不同的子序列

​​动态规划:115.不同的子序列 (opens new window)​​给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

本题虽然也只有删除操作,不用考虑替换增加之类的,但相对于​​动态规划:392.判断子序列 (opens new window)​​就有难度了,这道题目双指针法可就做不了。

  1. 当​​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]来匹配,都相同了指定要匹配啊。

例如: s:bagg 和 t:bag ,s[3] 和 t[2]是相同的,但是字符串s也可以不用s[3]来匹配,即用s[0]s[1]s[2]组成的bag。

当然也可以用s[3]来匹配,即:s[0]s[1]s[3]组成的bag。

所以当​​s[i - 1]​​​ 与 ​​t[j - 1]​​​相等时,​​dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]​​;

  1. 当​​s[i - 1] ​​​与 t[j - 1]不相等时,​​dp[i][j]​​​只有一部分组成,不用​​s[i - 1]​​​来匹配,即:​​dp[i - 1][j]​

所以递推公式为:​​dp[i][j] = dp[i - 1][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];
}

两个字符串的删除操作

​​动态规划:583.两个字符串的删除操作 (opens new window)​​给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符

  1. 当word1[i - 1] 与 word2[j - 1]相同的时候
  2. 当word1[i - 1] 与 word2[j - 1]不相同的时候

当word1[i - 1] 与 word2[j - 1]相同的时候,​​dp[i][j] = dp[i - 1][j - 1]​​;

当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:

情况一:删word1[i - 1],最少操作次数为​​dp[i - 1][j] + 1​

情况二:删word2[j - 1],最少操作次数为​​dp[i][j - 1] + 1​

情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为​​dp[i - 1][j - 1] + 2​

那最后当然是取最小值,所以当word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:​​dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1})​​;

状态转移方程:

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] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});
}

编辑距离

​​动态规划:72.编辑距离 (opens new window)​​给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

编辑距离终于来了,有了前面三道题目的铺垫,应该有思路了,本题是两个字符串可以增删改,比 ​​动态规划:判断子序列 (opens new window)​​​,​​动态规划:不同的子序列 (opens new window)​​​,​​动态规划:两个字符串的删除操作 (opens new window)​​都要复杂的多。

在确定递推公式的时候,首先要考虑清楚编辑的几种操作,整理如下:

  • if (word1[i - 1] == word2[j - 1])
  • 不操作
  • if (word1[i - 1] != word2[j - 1])

也就是如上四种情况。

if (word1[i - 1] == word2[j - 1]) 那么说明不用任何编辑,​​dp[i][j] ​​​就应该是 ​​dp[i - 1][j - 1]​​​,即​​dp[i][j] = dp[i - 1][j - 1]​​;

此时可能有同学有点不明白,为啥要即​​dp[i][j] = dp[i - 1​​][j - 1]呢?

那么就在回顾上面讲过的dp[i][j]的定义,word1[i - 1] 与 word2[j - 1]相等了,那么就不用编辑了,以下标​​i-2​​​为结尾的字符串word1和以下标​​j-2​​​为结尾的字符串word2的最近编辑距离​​dp[i - 1][j - 1] ​​​就是​​ dp[i][j]​​了。

在下面的讲解中,如果哪里看不懂,就回想一下​​dp[i][j]​​的定义,就明白了。

在整个动规的过程中,最为关键就是正确理解dp[i][j]的定义!

if (word1[i - 1] != word2[j - 1]),此时就需要编辑了,如何编辑呢?

  1. 操作一:word1增加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-2为结尾的word1 与 i-1为结尾的word2的最近编辑距离 加上一个增加元素的操作。

即 ​​dp[i][j] = dp[i - 1][j] + 1​​;

  1. 操作二:word2添加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个增加元素的操作。

即 ​​dp[i][j] = dp[i][j - 1] + 1​​;

这里有同学发现了,怎么都是添加元素,删除元素去哪了。

word2添加一个元素,相当于word1删除一个元素,例如 word1 = “ad” ,word2 = “a”,word2添加一个元素d,也就是相当于word1删除一个元素d,操作数是一样!

  1. 操作三:替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增加元素,那么以下标i-2为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个替换元素的操作。

即 ​​dp[i][j] = dp[i - 1][j - 1] + 1​​;

综上,当 if (word1[i - 1] != word2[j - 1]) 时取最小的,即:​​dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1​​;

递归公式代码如下:

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;
}

11 回文子串

  1. 回文子串


给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

输入:“abc” 输出:3 解释:三个回文子串: “a”, “b”, “c”

示例 2:

输入:“aaa” 输出:6 解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”

提示:

输入的字符串长度不会超过 1000


  1. 确定dp数组

布尔类型的​​dp[i][j]​​​:表示区间范围​​[i,j]​​ (注意是左闭右闭)的子串是否是回文子串,如果是​​dp[i][j]​​为true,否则为false。

  1. 确定地推公式

在确定地推公式时,就要分析如下几种情况。

  1. 当s[i]与s[j]不相等,那没啥好说的了,dp[i][j]一定是false。
  2. 当s[i]与s[j]相等时,这就复杂一些了,有如下三种情况
  • 情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串
  • 情况二:下标i 与 j相差为1,例如aa,也是回文子串
  • 情况三:下标:i 与 j相差大于1的时候,例如cabac
  • 此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是​​ i+1​​​ 与​​j-1​​区间
  • 这个区间是不是回文就看​​dp[i + 1][j - 1]​​是否为true。
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;
}
}
  1. 数组初始化

初始化的时候全部为false

  1. 遍历顺序

从递推方程可以看出,由于​​dp[i+1][j-1]​​​在​​dp[i][j]​​下方,未初始化就使用了

因此需要从下到上从左到右遍历

​dp[i][j]​

​dp[i-1][j+1]​

  1. 举例推导

输入:“aaa”

LeetCode动态规划基础题-子字符串问题(13题)_leetcode_11

图中有6个true,所以就是有6个回文子串。

注意因为dp[i][j]的定义,所以j一定是大于等于i的,那么在填充dp[i][j]的时候一定是只填充右上半部分

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

12 最长回文子串

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

  1. 最长回文子串


示例 1:

输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:

输入:s = “cbbd”
输出:“bb”


最大的区别在于:

这里只需要统计最长的回文串,而不是统计回文串

所以只需修改的:

添加最大的连续长度的判断

if(dp[i][j] && j - i + 1 >= maxLen){
maxLen = j - i + 1;
start = i;
end = j;
}
class Solution {
public:
string longestPalindrome(string s) {
vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
if(s.size() <= 1 ) return s;
int result = 0;
int maxLen = 0;
int start = 0;
int end = 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) { // 情况一 和 情况二
dp[i][j] = true;
} else if (dp[i + 1][j - 1]) { // 情况三
result++;
dp[i][j] = true;
}
}
if(dp[i][j] && j - i + 1 >= maxLen){
maxLen = j - i + 1;
start = i;
end = j;
}
}
}
return s.substr(start,end - start + 1);
}
};

13 最长回文子序列

  1. 确定dp数组(dp table)以及下标的含义

dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为​dp[i][j]​

  1. 确定递推公式

如果s[i]与s[j]相同,那么​​dp[i][j] = dp[i + 1][j - 1] + 2​​;

LeetCode动态规划基础题-子字符串问题(13题)_子序列_12

如果不相等则取两端部分的最大值分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列。

​dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])​​;

  1. 初始化

根据定义 ​​i -> i​​​为1, ​​初始化为0​

  1. 遍历顺序

和之前一样,防止​​dp[i+1][j-1]​​​被覆盖,同时要考虑​​dp[i+1][j]​

需要注意的是由于​​dp[i+1][j]​​ 所以j+1开始遍历

class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.size();
if(n <= 1){
return n;
}
// dp[i][j] 表示[i][j]的最长回文子序列的长度
vector<vector<int>> dp(n+1, vector<int>(n+1,0));
for(int i = 0; i <=n; i++){
dp[i][i] = 1;
}
for(int i = n-1; i >= 0; --i){
for(int j = i + 1; j <n; ++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][n-1];
}
};

二、小结

LeetCode动态规划基础题-子字符串问题(13题)_leetcode_13



举报

相关推荐

0 条评论