目录
- 32. 最长有效括号
- 思路
- 代码
- 300. 最长递增子序列
- 思路
- 代码
- 674. 最长连续递增序列
- 思路1:双指针
- 代码1:双指针
- 思路2:dp
- 代码2:dp
- 718. 最长重复子数组
- 思路1:dp
- 代码1:dp
- 思路2:dp优化
- 代码2:dp优化
- 1143. 最长公共子序列
- 思路
- 代码
- 1035. 不相交的线(最大连线数)
- 思路
- 代码
- 53. 最大子序和
- 思路1:贪心
- 代码1:贪心
- 思路2:dp
- 代码2:dp
32. 最长有效括号
题目🔗
给你一个只包含 '('
和 ')'
的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例 2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例 3:
输入:s = ""
输出:0
思路
首先,我们定义一个dp数组,表示以第i个元素结尾的字符串的最长有效括号的长度。
对于dp[i]
来说,它可能有以下两种情况:
s[i] == '('
:这时他是无法和前面的括号组成有效括号对的,所以dp[i] = 0
。s[i] == ')'
:这时我们需要确定它是否能和前面的元素组成有效括号对,那么还需要去观察s[i-1]
:s[i-1] == '('
:s[i-1]
刚好和s[i]
组成一对有效括号,长度为2。那么这种情况下我们可以推导出: d p [ i ] = d p [ i − 2 ] + 2 dp[i] = dp[i-2] + 2 dp[i]=dp[i−2]+2s[i-1] == ')'
:对于这种情况来说,我们不知道s[i-1]
是否和前面的元素组成有效括号,但无论如何,dp[i-1]
中存放的总是以s[i-1]
结尾的字符串的最长有效括号长度。假设s[i-1]
是有效的括号对之一,那么与他配对的括号index就是i-dp[i-1]
,于是乎我们就可以找到和s[i]
配对的位置i-dp[i-1]-1
,这样如果s[i-dp[i-1]-1] == '('
,那么s[i]
就能与他配对上。那么我们就可以推导出: d p [ i ] = d p [ i − 1 ] + 2 + d p [ i − d p [ i − 1 ] − 2 ] dp[i] = dp[i-1] + 2 + dp[i-dp[i-1]-2] dp[i]=dp[i−1]+2+dp[i−dp[i−1]−2]
注意,上面要需要加上
d
p
[
i
−
d
p
[
i
−
1
]
−
2
]
dp[i-dp[i-1]-2]
dp[i−dp[i−1]−2],这是因为如果我们判定s[i-dp[i-1]-1] == '('
与s[i]
配对的话,还需要考虑s[i-dp[i-1]-1]
之前的情况。
代码
class Solution {
public:
int longestValidParentheses(string s) {
vector<int> dp(s.size(), 0);
int ans = 0;
for(int i = 1; i < s.size(); ++i){
if(s[i] == ')'){
// 前面一个是(的情况,直接配对成功
if(s[i - 1] == '('){
dp[i] = 2;
// 加上前面配对的数量
if(i - 2 >= 0) dp[i] += dp[i - 2];
}
// 前面一个也是),但是有配对的
else if(dp[i - 1] > 0){
// 判断匹配位置的符号是不是(,如果是则可以配对
if((i - dp[i - 1] - 1) >= 0 && s[i - dp[i - 1] - 1] == '('){
dp[i] = dp[i - 1] + 2;
// 加上前面配对的数量
if(i - dp[i - 1] - 2 >= 0)
dp[i] += dp[i - dp[i - 1] - 2];
}
}
}
ans = max(ans, dp[i]);
}
return ans;
}
};
300. 最长递增子序列
题目🔗
给你一个整数数组 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
思路
dp[i]
表示第i个数为结尾的子序列的最长严格递增长度。
很容易的就能想到,我们要计算的dp[i]
一共有两种情况:
- nums[i] > nums[i - 1]
:此时dp[i] = dp[i-1] + 1
- nums[i] < nums[i - 1]
:此时dp[i] = dp[i-1]
但是如果我们简单的将代码写成这样:
int lengthOfLIS(vector<int>& nums) {
vector<int> dp(nums.size(), 1);
for(int i = 1; i < nums.size(); ++i){
if(nums[i] > nums[i - 1])
dp[i] = dp[i - 1] + 1;
else
dp[i] = dp[i-1];
}
return dp[nums.size()-1];
}
当测试用例为:nums = [4,10,4,3,8,9]
。我们打印出上面代码计算出的dp数组:1 2 2 2 3 4
。可以发现并不是我们所期望的那样,当i=4
时,dp[4]
应该为2
,而不是3
。
我们计算成3
是因为:对于子数组[4,10,4,3]
来说,它的最大递增子序列为4, 10
。我们如果只是简单的判断nums[4] > nums[3]
,就执行dp[i] = dp[i - 1] + 1
,那么就相当于是把4, 10, 8
当成了最大递增子序列,然而它并不是的,所以这里就出现了判断失误。
正确的做法是,我们要把dp[i]
定义为第i
个数为结尾的子数组的最长严格递增长度,并且该最长严格递增子序列最后一个数必定是nums[i]
。
每次进行判断的时候,我们要对该子数组各个位置nums[j]
进行遍历,并比较与nums[i]
的大小,如果nums[i] > nums[j]
,那么就有dp[i] = max(dp[i], dp[j] + 1)
。
最终答案就是dp数组中最大的那个数。
代码
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> dp(nums.size(), 1);
int ans = 1;
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);
}
ans = max(ans, dp[i]);
}
return ans;
}
};
674. 最长连续递增序列
题目🔗
给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
连续递增的子序列 可以由两个下标 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
。
思路1:双指针
定义两个指针:int slow
和int fast
。
代码1:双指针
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
int slow = 0;
int ans = 0;
while(slow < nums.size()){
int fast = slow + 1;
while(fast < nums.size() && nums[fast] > nums[fast - 1]){
fast++;
}
ans = max(ans, fast - slow);
slow = fast;
}
return ans;
}
};
思路2:dp
dp的思路和上题类似。我们定义一个dp数组,dp[i]
表示第i个数为结尾的子数组的最长连续递增长度,并且该最长连续递增序列最后一个数必定是nums[i]
。
与上题不一样的就是我们不在需要遍历子数组了,直接判断nums[i]
和nums[i - 1]
的大小即可。
代码2:dp
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
vector<int> dp(nums.size(), 1);
int ans = 1;
for(int i = 1; i < nums.size(); ++i){
if(nums[i] > nums[i - 1]){
dp[i] = max(dp[i], dp[i - 1] + 1);
}
ans = max(ans, dp[i]);
}
return ans;
}
};
718. 最长重复子数组
题目🔗
给两个整数数组 nums1
和 nums2
,返回 两个数组中 公共的 、长度最长的子数组的长度 。
示例 1:
输入:nums1 = [1,2,3,2,1]
, nums2 = [3,2,1,4,7]
输出:3
解释:长度最长的公共子数组是 [3,2,1]
。
示例 2:
输入:nums1 = [0,0,0,0,0]
, nums2 = [0,0,0,0,0]
输出:5
思路1:dp
dp[i][j]
表示数组nums1
前i
个元素和数组nums2
前j
个元素的最长公共子数组的长度。其实也是和上题一样的。
代码1:dp
我们可以很快写出代码:
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size(), vector<int>(nums2.size(), 0));
int ans = 0;
// dp数组初始化
for(int i = 0; i < nums1.size(); ++i){
if(nums2[0] == nums1[i]) dp[i][0] = 1;
if(dp[i][0] > ans) ans = dp[i][0];
}
for(int j = 0; j < nums2.size(); ++j){
if(nums1[0] == nums2[j]) dp[0][j] = 1;
if(dp[0][j] > ans) ans = dp[0][j];
}
// dp数组推导
for(int i = 1; i < nums1.size(); ++i){
for(int j = 1; j < nums2.size(); ++j){
if(nums1[i] == nums2[j])
dp[i][j] = dp[i - 1][j - 1] + 1;
if(dp[i][j] > ans)
ans = dp[i][j];
}
}
return ans;
}
};
注意初始化的时候我们也要去更新ans的大小,不然会漏结果。
思路2:dp优化
其实,我们可以将dp[i][j]
定义为数组nums1
前i-1
个元素和数组nums2
前j-1
个元素的最长公共子数组的长度,也就是在nums1前增加一行,nums2前增加一列:
这样我们在定义数组的时候就已经初始化好了,不必额外去for循环初始化:vector<vector<int>> dp (nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
代码2:dp优化
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
int ans = 0;
// dp数组推导
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;
if(dp[i][j] > ans)
ans = dp[i][j];
}
}
return ans;
}
};
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
。
思路
这题和上题的不同之处在于 公共子序列和原字符串是相对有序的。感觉这题有点像300. 最长递增子序列和718. 最长重复子数组的结合体。
上一题以i-1
和j-1
是因为子数组必须要求是连续的,如果不连续,公共子数组直接归零,下一个子数组不能继承前一个子数组的公共子数组长度。
子序列则不一样,允许中间有间隔,下一个子序列可以继承前一个子序列的公共子序列长度。
这样说很抽象,我们举个例子。比如说两个数组nums1 = [1,2,3,4,5]
, nums2 = [1,2,3,8,5]
。在index=3
的时候出现分歧了,如果是公共子数组,到index=3
时,其公共子数组必须要归零,如果不归零,会影响index=4
的判断。而如果是公共子序列,index=3
可以保留index=2
的最长子序列数,继而在index=4
时继续递增。
代码
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int len1 = text1.size();
int len2 = text2.size();
vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
for(int i = 1; i <= len1; ++i){
for(int j = 1; j <= len2; ++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[len1][len2];
}
};
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
思路
和1143. 最长公共子序列一模一样。
代码
class Solution {
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
int len1 = nums1.size();
int len2 = nums2.size();
vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
for(int i = 1; i <= len1; ++i){
for(int j = 1; j <= len2; ++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[len1][len2];
}
};
53. 最大子序和
题目🔗
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1]
的和最大,为 6
。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
思路1:贪心
因为数组中包含负数,所以会拉低连续子数组的和,甚至变为负数,这个时候就说明从前面计算到这里的负数,再继续计算下去肯定只会越来越小,所以我们要放弃前面的元素,从下一个元素开始重新计算。每次计算的时候我们都要记录更大的连续和,最后得到的就是全局最优的最大连续和。
代码1:贪心
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int result = INT32_MIN;
int count = 0;
for(int i = 0; i < nums.size(); i++) {
count += nums[i];
if(count > result) result = count;
if(count <= 0) count = 0;
}
return result;
}
};
思路2:dp
dp[i]
表示以nums[i-1]
为结尾的最大子数组的和为dp[i]
。
对于每个dp[i]
都有两种情况:
nums[i-1]
加入到前面的子数组中,也就是dp[i] = dp[i - 1] + nums[i - 1]
nums[i-1]
不加入到前面的子数组中,从它这里重新开始计算和,也就是dp[i] = numd[i - 1]
- 我们取最大的值:
dp[i] = max(nums[i - 1], dp[i - 1] + nums[i - 1])
代码2:dp
class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int> dp(nums.size() + 1, 0);
int ans = INT_MIN;
for(int i = 1; i <= nums.size(); ++i){
dp[i] = max(nums[i - 1], dp[i - 1] + nums[i - 1]);
if(dp[i] > ans) ans = dp[i];
}
return ans;
}
};