1143.最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
思路:
1.Day56的求重复子数组本质上是求最长连续子序列,而本题实际上就是求最长子序列,又回到了要不要看连续的问题,但大体思路实际上与求重复子数组一致,有了前一道题的基础,本题至少能够有思路了。
2.首先想dp数组的含义,dp[i][j]表示text1中区间为[0 , i - 1]和text2中区间为[0, j - 1]的两个字符串的最长公共子序列长度。
3.然后想递推公式。我们通过比较当前的text1[i]与text2[j]划分出两种情况:相等或不等。相等的情况下,那么直接在dp[i - 1][j - 1]的基础之上加1即可;如果不相等,那么就在dp[i - 1][j]和dp[i][j - 1]中取最大的,相当于就是分别在text1中取[0, i -2]和text2中取[0, j - 1],与在text1中取[0, i -1]和text2中取[0, j - 2]拿到的两个最大公共子序列长度求最大值。
4.然后想初始化,dp[i][0]与dp[0][j]根据dp数组定义来看是没有意义的,但方便递推我们初始化为0,而其他的我们也一并初始化为0即可。
5.最后想遍历顺序,如果我们以i为行以j为列画出一个二维矩阵,通过递推公式不难发现dp[i][j]能够由dp[i - 1][j - 1],dp[i][j - 1],dp[i - 1][j]推导出来,即能够从左上方推导出来,因此我们的遍历顺序是从上向下,从左往右(即对于i和j都是从小到大遍历)。
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
//dp数组表示对于[0, i - 1]的text1,[0, j - 1]的text2的最长公共子序列
vector<vector<int>> dp(text1.size() + 1, vector(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;
}
//比较的两个字符不相等,则看[0,i - 2]的text1与[0, j - 1]的text2的最长公共子序列
//和[0,i - 1]的text1与[0, j - 2]的text2的最长公共子序列
else{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[text1.size()][text2.size()];
}
};
启发:
1.通过本题在看完讲解和联系之前的一系列动规题目后,恍然了dp数组在本系列问题中为什么要选择二维,因为实际上以前的大部分题目理论上来说也都是二维,用一个维度代表元素一个维度代表状态,只是我们简化后可以只用一维表示状态;而在本系列问题中尤其是涉及到两个数组进行比较,此时我们需要分别表示两个数组的不同状态,此时一维是完全不可能做到的,因此必须得使用二维。因此dp数组的维度一定程度上是根据我们所需要记录的状态来决定的
2.通过抽象画出二维矩阵再结合递推公式,我们可以很轻松的得出我们的遍历顺序,在这之前可能大多数时候是想当然的想出递推公式,经过这一题后发现原来可以通过具体的矩阵来顺理成章推出遍历顺序。
1035.不相交的线
在两条独立的水平线上按给定的顺序写下 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 的直线相交。
示例 2:
输入:nums1 = [2,5,1,2,5], nums2 = [10,5,2,1,5,2]
输出:3
示例 3:
输入:nums1 = [1,3,7,1,7,5], nums2 = [1,9,2,5,1]
输出:2
思路:
1.本题在刚做完前一道题的时候来看会发现实际上就是上一道题,但既然是分析思路那自然得站在没有看过上一道题的基础上来看。本题很容易被所谓的连线给绕进去,会一直考虑如何避免连线相交。
但实际上在有了前面子序列题目的基础后我们不难发现,所谓的连线不能相交实际上就是保证元素顺序一定,例如示例2中nums1[0] = nums2[2] = 2,nums1[1] = nums2[4] =5,对于两个数组中的5都要保证是在1之后,不能让nums[1]的5与nums2[1]的5相连,因为此时nums2中5没有在1之后。
所以实际上就是保证连线元素的顺序一定,和前面求子序列的情况一比较就发现实际上是一模一样的。所以本题代码实际上和上一题也在除了命名之外完全一致。
class Solution {
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 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;
}
else{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[nums1.size()][nums2.size()];
}
};
启发:
1.本题实际上真的很容易绕进直线相交的泥潭里去然后出不来,然而看破后会发现直线相交实际上只是子序列套的一层壳,要学会通过现象看本质。
53.最大子数组和
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
思路:
1.本题实际上是贪心算法中的一道题,那时候已经用贪心算法解决了,回过头来用动规看这道题时发现贪心思路其实时给了自己很大帮助。
2.首先想dp数组含义,dp[i]表示数组中以i结尾的最大子数组和。
3.然后想递推公式,显然对于当前元素,要么选择在前面元素连续和基础上加上当前元素,要么直接从当前元素开始重新计算,因此dp[i] = max(dp[i - 1] + nums[i], dp[i])
4.然后想初始化,通过递推公式可以看出是通过dp[0]推导出后面所有元素,而dp[0]显然是初始化为nums[0],后面的全部初始化为0即可,不影响最终结果。
5.最后想遍历顺序,很明显本题是从前往后遍历。
贪心:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int count = 0;
int result = INT_MIN;
for(int i = 0; i < nums.size(); i++){
count += nums[i];
result = max(result, count);
//如果连续和已经小于0,那么重置
if(count <= 0){
count = 0;
}
}
return result;
}
};
动规:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
if(nums.size() == 1) return nums[0];
vector<int> dp(nums.size(), 0);
int result = nums[0];
dp[0] = nums[0];
for(int i = 1; i < nums.size(); i++){
dp[i] = max(dp[i - 1] + nums[i], nums[i]);
result = max(result, dp[i]);
}
return result;
}
};
启发:
1.本题再一次提醒自己关于最后返回的结果,实际上是和dp数组的含义紧密相连的,有的题目dp数组最后的元素实际上就是我们返回的结果,但有的题目(例如本题),结果并不一定是在dp数组的最后元素位置取得,因此我们一定要根据dp数组的含义和具体题目具体分析。