题目描述
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例: 输入:
nums = [10,9,2,5,3,7,101,18]输出:4解释:最长递增子序列是[2,3,7,101],因此长度为 4。
思路分析
动态规划朴素解法:dp[i] 代表以i结尾的 LIS 长度,两层循环,时间复杂度 \(O(n^2)\)。 当数据量变大时,\(O(n^2)\)会超时,于是引入贪心 + 二分查找优化到 \(O(n\log n)\)。
贪心核心思想
维护数组length: length[k] 含义:长度为 k+1 的上升子序列,末尾元素尽可能小。
为什么要让末尾尽可能小? 末尾数字越小,后面越容易接更大的数字,更容易构造更长的上升子序列。
处理逻辑:
- 遍历原数组每一个元素
nums[i] - 如果
nums[i]大于length数组末尾元素:直接尾插,代表找到了更长的子序列 - 否则:在
length数组中,二分查找第一个 ≥ nums i 的位置 ,用nums[i]替换该位置的值。更新对应长度子序列的最小末尾。
⚠️重点:length数组并不是真实的最长上升子序列,它只用来记录各长度子序列的最小末尾,最终数组的大小才是 LIS 的长度。
cpp
class Solution {
public:
int BinarySearch(int left, int right, vector<int>& num, int target) {
while (left < right) {
int mid = left + (right - left) / 2;
if (num[mid] >= target) {
right = mid;
} else if (num[mid] < target) {
left = mid + 1;
}
}
return left;
}
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> length;
length.push_back(nums[0]);
for (int i = 1; i < n; i++) {
if (nums[i] > length.back())
length.push_back(nums[i]);
else {
int pos = BinarySearch(0, length.size(), length, nums[i]);
length[pos] = nums[i];
}
}
return length.size();
}
};