LeetCode 300.最长递增子序列
题目链接:300. 最长递增子序列 - 力扣(Leetcode)
首先尝试自己解题,将dp数组的dp[i]定义为以nums[i]结尾的数组中,最长递增子序列的长度,由于子序列不要求为连续数组,对于nums[i],倒序遍历位于nums[i]之前的数字,如果nums[i]比nums[j]大(严格递增不能相等),nums[i]就能和nums[j]及其之前可能包含的其他数字构成递增子序列,因此dp[i]能在dp[j]的基础上+1。本来我想的是倒序遍历找到第一个可以构成子序列的应该就是可能的最长子序列,后来才发现数组无序状态下不能提前终止循环,所以内层循环需要遍历完整,正序遍历也可以,并且dp[i]在遍历过程中取最大值。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
// dp[i]以nums[i]结尾的最长递增子序列的长度
vector<int> dp(n);
dp[0] = 1; // 初始化dp[0]为1(一个数是严格递增的)
int result = dp[0]; // 初始化结果
for (int i=1; i<n; i++){
// j从i倒序遍历
for(int j=i-1; j>=0; j--){
if (nums[i] > nums[j]){
dp[i] = max(dp[i], dp[j] + 1);
// break; // 提前终止循环
}
}
if (dp[i] == 0) dp[i] = 1; // nums[i]之前没有递增的
result = max(result, dp[i]);
}
return result;
// return dp[n-1];
}
};
自己做的时候,发现没办法从测试用例确定返回结果能不能直接返回dp[n-1], 所以还是用result在遍历过程中收集可能的最大结果。用return dp[n-1]的代码提交之后后果然出问题了。从报错的测试用例能看到,dp数组的最大值并不一定是dp[n-1],因为最长递增子序列可能不包含nums的最后一个数,而dp[n-1]定义为包含nums[n-1],这是需要注意的细节。
算法性能分析上,两层for循环遍历使得时间复杂度是O(n^2),空间复杂度是O(n)。
LeetCode 674 最长连续递增序列
题目链接:674. 最长连续递增序列 - 力扣(Leetcode)
这一题和300类似,也是无序数组求最长递增序列,但要求是连续的序列。但其实这题比递增子序列还要简单一些,因为nums[i]只需要和nums[i-1]比较,只有nums[i] > nums[i-1]时,dp[i]才能从dp[i-1]推导过来,否则递增的连续序列从当前的nums[i]开始重新计算。更简洁的写法是将dp数组初始化为1,这样遍历时只需要在满足if条件时更新。
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n, 0);
dp[0] = 1;
int result = dp[0];
for (int i=1; i<n; i++){
if(nums[i] > nums[i-1]) dp[i] = dp[i-1] + 1;
else dp[i] = 1;
result = max(result, dp[i]);
}
return result;
}
};
同样地,最长连续递增序列不一定是dp[n-1],因此需要在遍历过程中收集可能的最大结果,总的时间和空间复杂度都是O(n)。
LeetCode 718 最长重复子数组
题目链接:718. 最长重复子数组 - 力扣(Leetcode)
这一题要找到两个数组最长的公共连续子数组,暴力解法就是遍历两个数组,找到两个数组相同的数字,以此为开头继续遍历找公共数组:
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size();
int n = nums2.size();
int result = 0; // 结果初始化
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
if(nums2[j] != nums1[i]) continue;
// 找到nums2[j] == nums1[i], 找到一个公共子数组开头
int k = 1, res = 1;
while(i + k < m && j + k < n && nums1[i + k] == nums2[j + k]){
res++;
k++;
}
if(res > result) result = res; // 收集结果
}
}
return result;
}
};
由于时间复杂度是O(mnk),即便数据长度最长只有1000,提交还是超时了。而用动态规划思路解题,直觉需要二维的dp数组,i,j分别对应两个数组时,dp[i][j]表示以nums1[i]和nums2[j]开头的最长重复子数组的长度。当nums1[i]==nums2[j]时,如果前一个推导状态dp[i-1][j-1]不为0,就能由dp[i-1][j-1]+1得到dp[i][j],否则dp[i][j]=1;nums1[i]!=nums2[j]时仍为初始化状态0,同时在遍历过程中用result收集可能的最大结果。
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size();
int n = nums2.size();
vector<vector<int>> dp(m, vector<int>(n, 0));
int result = 0; // 结果初始化
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
// 对应数字不相等时直接跳过
if(nums2[j] != nums1[i]) continue;
// 下标限制条件+对角线上的状态不为0时,表明当前相等数字可以和之前的相等数字子数组
// 一起构成长度+1的重复子数组
if(i > 0 && j > 0 && dp[i-1][j-1] != 0) dp[i][j] = dp[i-1][j-1] + 1;
else dp[i][j] = 1; // 否则子数组长度重新计数
if(dp[i][j] > result) result = dp[i][j]; // 收集结果
}
}
return result;
}
};
通过状态传递可以将时间复杂度优化O(mn),空间复杂度也是O(mn)。