文章目录
- 1143.最长公共子序列
- 思路
- CPP代码
- 1035.不相交的线
- 53.最大子序列和
- 思路
- CPP代码
1143.最长公共子序列
力扣题目链接
文章讲解:1143.最长公共子序列
视频讲解:动态规划子序列问题经典题目 | LeetCode:1143.最长公共子序列
本题其实就跟718.最长重复子数组类似,不要求连续了,但是还是要求相对顺序的。
思路
- 确定dp数组下标及其含义
和之前718.最长重复子数组套路一样,唯一的区别只体现在递推公式中。我们还是使用一个二维dp来表达
dp[i][j]
:长度为[0, i - 1]
的字符串text1与长度为[0, j - 1]
的字符串text2的最长公共子序列为dp[i][j]
- 确定递推公式
主要就是两大情况: text1[i - 1]
与 text2[j - 1]
相同,text1[i - 1]
与 text2[j - 1]
不相同
如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以dp[i][j] = dp[i - 1][j - 1] + 1
如果text1[i - 1] 与 text2[j - 1]不相同,那就看看text1[0, i - 2]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j - 2]的最长公共子序列,取最大的。
即:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
- dp数组如何初始化
先看看dp[i][0]
应该是多少呢?
test1[0, i-1]和空串的最长公共子序列自然是0,所以dp[i][0] = 0
同理dp[0][j]
也是0。
其他下标都是随着递推公式逐步覆盖,初始为多少都可以,那么就统一初始为0。
- 确定遍历顺序
从递推公式可以看出,我们分别从三个方向(当前格的左上、左、上)来得出当前格的值
所以肯定是从前往后,从上到下遍历矩阵
- 打印
以输入:text1 = “abcde”, text2 = “ace” 为例,dp状态如图:
CPP代码
class Solution {
public:
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()];
}
};
1035.不相交的线
力扣题目链接
文章讲解:1035.不相交的线
视频讲解:动态规划之子序列问题,换汤不换药 | LeetCode:1035.不相交的线
状态:把相同元素连成线,找出那些不相交的线,其实就是啥啊,求两个字符串的最长公共子序列长度
在上题中,我们已经讲过了1143.最长公共子序列
直接copy,就能通过
class Solution {
public:
int maxUncrossedLines(vector<int>& A, vector<int>& B) {
vector<vector<int>> dp(A.size() + 1, vector<int>(B.size() + 1, 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;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[A.size()][B.size()];
}
}
53.最大子序列和
力扣题目链接
文章讲解:53.最大子序列和
视频讲解:看起来复杂,其实是简单动态规划 | LeetCode:53.最大子序和
状态:之前我们用贪心算法写过一次本题贪心算法:最大子序和,其实也很简单,这次用动规写一遍,也很简单
思路
- dp数组含义
dp[i]:包括下标i(以nums[i]为结尾)的最大连续子序列和为dp[i]。
- 递推公式
dp[i]只有两个方向可以推出来:
-
dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和
-
nums[i],即:从头开始计算当前连续子序列和
一定是取最大的,所以dp[i] = max(dp[i - 1] + nums[i], nums[i]);
需要注意的是,本题的最大值可不一定存在与dp数组的最后一个元素,因为根据dp[i]
的定义是以nums[i]为结尾)的最大连续子序列和。所以后续我们必须用一个容器来装dp数组的最大值,免得最后还要再遍历一遍。
dp[i] = max(dp[i - 1] + nums[i], nums[i]); // 状态转移公式
if (dp[i] > result) result = dp[i]; // result 保存dp[i]的最大值
- 初始化
0下标初始化为nums[0]
其他均初始化为0
- 遍历顺序
从小到大遍历
- 打印
以示例一为例,输入:nums = [-2,1,-3,4,-1,2,1,-5,4],对应的dp状态如下:
CPP代码
class Solution {
public:
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;
}
};