题目链接
最长递增子序列
题目描述
注意点
- 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序
解答思路
- 初始想到使用动态规划解决本题,对于任意位置元素,找到前面比其值更小的元素,并根据前面元素的最长递增子序列计算出该元素的最长递增子序列,遍历完整个数组后就能得到结果,但是时间复杂度为O(n²),比较耗时
- 参考题解使用贪心算法+二分查找解决本题,从头开始遍历数组,想要找到最长递增子序列,则相同长度的子序列的尾节点应该尽可能小,所以在遍历到某个元素x时,应该用x覆盖掉比x大的元素中最小的那个尾节点,而找到比x大的元素中最小的那个尾节点则应该用二分查找,时间复杂度为O(nlogn)
代码
方法一:
// 动态规划
class Solution {
public int lengthOfLIS(int[] nums) {
int res = 0;
int n = nums.length;
int[] dp = new int[n];
dp[0] = 1;
for (int i = 0; i < n; i++) {
dp[i] = 1;
for (int j = i - 1; j >= 0; j--) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
res = Math.max(res, dp[i]);
}
return res;
}
}
方法二:
// 贪心+二分查找
class Solution {
public int lengthOfLIS(int[] nums) {
int res = 0;
// 存储不同长度中末尾元素的最小值
int[] tails = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
int left = 0, right = res;
// 当前元素覆盖掉比它大的元素中最小的那个
while (left < right) {
int mid = (left + right) / 2;
if (nums[i] > tails[mid]) {
left = mid + 1;
} else {
right = mid;
}
}
tails[left] = nums[i];
if (right == res) {
res++;
}
}
return res;
}
}
关键点
- 动态规划的思想
- 贪心算法的思想:如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小
- 二分查找寻找比当前元素大的元素中最小的那个的写法