【LeetCode-300】最长递增子序列
问题
给定一个无序的整数数组,找到其中最长递增子序列的长度。
示例
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的递增子序列是 [2,3,7,101],它的长度是 4。
解答1:动态规划
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int res = 0, n = nums.size();
vector<int> dp(n, 1);
for (int i = 1; i < n; i++) {
for(int j = 0; j < i; j++)
if (nums[j] < nums[i]) dp[i] = max(dp[j] + 1, dp[i]);
res = max(res, dp[i]);
}
return res;
}
};
重点思路
记动态规划数列dp,用于存储以该数结尾的最长上升子序列长度,最终输出dp数列中最大的值。时间复杂度O(n^2)。
解答2:贪心算法+二分查找
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if (nums.empty()) return 0;
vector<int> res{nums[0]};
for(int i = 1; i < nums.size(); i++) {
if (res.back() < nums[i]) res.push_back(nums[i]);
else { // 二分查找
int left = 0, right = res.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (lis[mid] < nums[i]) left = mid + 1;
else right = mid;
}
res[left] = nums[i];
}
}
return res.size();
}
};
重点思路
考虑一个简单的贪心,如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。
维护一个数列lis
,用于存放上升最慢的递增子序列。首先遍历输入数组,当前数的值为nums[i]
,如果该数大于lis
中最后的数,则pushback到最后,否则查找比nums[i]
小且差的绝对值最小的一个数,用nums[i]
替换该数对应的后一个数,保证递增的前提下使该序列上升得尽可能慢。
然后就是找这个需要替换的数的问题了。因为是递增数列,所以肯定用二分查找快。由上面的推导可知,从lis中取一个数,当这个数比nums[i]
小时,那么需要替换的数肯定在这个数的后面。所以可以得到二分查找最关键的一句判断if (lis[mid] < nums[i]) left = mid + 1
,剩下的就无脑写了。时间复杂度为O(nlog(n))。
解答3:贪心算法+二分查找(库函数lower_bound)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if (nums.empty()) return 0;
vector<int> res{nums[0]};
for(int i = 1; i < nums.size(); i++) {
if (res.back() < nums[i]) res.push_back(nums[i]);
else *lower_bound(res.begin(), res.end(), nums[i]) = nums[i];
}
return res.size();
}
};
拓展
输出nums的最长递增子序列。(如果有多个答案,请输出其中 按数值(注:区别于按单个字符的ASCII码值)进行比较的 字典序最小的那个)
解答1:贪心算法+二分查找+恢复序列
class Solution {
public:
vector<int> LIS(vector<int>& nums) {
if (nums.empty()) return {};
int n = nums.size();
int maxLen[n]; maxLen[0] = 1;
vector<int> res{nums[0]};
for (int i = 1; i < n; i++) {
if (res.back() < nums[i]) {
res.push_back(nums[i]);
maxLen[i] = res.size();
}
else {
int pos = lower_bound(res.begin(), res.end(), nums[i]) - res.begin();
res[pos] = nums[i];
maxLen[i] = pos + 1; // 以当前数字结尾的最长递增子序列
}
}
for (int i = res.size() - 1, j = n - 1; i >= 0; j--) // i for res, j for nums
if (maxLen[j] == i + 1) res[i--] = nums[j];
return res;
}
};
重点思路
先使用贪心算法得到最长子序列长度,同时记录以当前位置结尾的子数组的最长递增子序列。最后我们得到一个maxLen数组,例如[1,2,3,2,3]
,我们需要判断maxLen[2]
与maxLen[4]
选哪个。由于我们需要字典序最小,如果我们选maxLen[2]
,则意味着必须保证nums[2] < nums[4]
,此时maxLen[4] = 4
,矛盾,所以有相同的数时取靠后的。