//给你一个整数数组 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
//
//
//
//
// 提示:
//
//
// 1 <= nums.length <= 2500
// -104 <= nums[i] <= 104
//
//
//
//
// 进阶:
//
//
// 你可以设计时间复杂度为 O(n2) 的解决方案吗?
// 你能将算法的时间复杂度降低到 O(n log(n)) 吗?
//
// Related Topics 二分查找 动态规划
// 👍 1366 👎 0
这题其实挺复杂的,要求递增子序列 [491]递增子序列,而且要最长递增子序列。
那么如果昨晚491题,直接找出最长的长度即可。
那么491题的时间复杂度为 O(2 n),再选择出最长的长度,就是O(2 n )。这个复杂度显然有些过高。
那么重新看题,题目要求返回最长严格递增子序列的 长度 。
只要求长度,不需要找到具体的子序列。
一 dp
子序列要求的是顺序固定,不一定重复, 那么使用动态规划的做法,dp数组保存的是以i位置结尾,最长的递增子序列长度,且i必须在子序列中。
因为这样就可以只判断结束的数来确定整个递增子序列的最大值,如果nums[cur]大于nums[i],dp[cur] = dp[i]+1。这就代表将cur和i为结尾的递增子序列组成一个新的,长度+1的递增子序列。
class Solution {
public int lengthOfLIS(int[] nums) {
// 如果数组都没有数字,就不存在递增子序列,直接返回0
if (nums.length == 0) {
return 0;
}
// dp数组,表达以i结尾的递增子序列长度
int[] dp = new int[nums.length];
// 只有一个数的时候,他自己就是递增的
dp[0] = 1;
int res = 1;
for (int i = 1; i < nums.length; i++) {
dp[i] = 1;
// 依次从i前面找
for (int j = 0; j < i; j++) {
// 找到小于当前数的位置,就可以和当前位置组成一个长度+1的递增子序列
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[j] + 1, dp[i]);
}
}
res = Math.max(res, dp[i]);
}
return res;
}
}
二 贪心
如果我们想要让递增子序列尽量的长,那么就需要使子序列中的数尽量的小。
比如[0,1,2,4,3] 可以找出两个递增子序列,[0,1,2,4]和[0,1,2,3]。
那么如果选择小的那个,当最后再新增一个数字4的时候,就可以组成一个长度为5的递增子序列,而大的那个无法组成更长的递增子序列。
使用一个数组d来保存i长度的递增子序列最后一个数字。
那么如何让子序列中的数字尽量小呢?
因为d保存的是递增子序列的最后一个数字,不同长度的子序列最后一位必然是递增的,因此d是一个单调递增的趋势。
举个例子 [0,3,12,2], 这个数组,他对应的d数组是[0,2,12], [0,2,12]显然不是一个递增子数组,因为顺序已经发生了改变。但是表达的意思是,长度为1的递增子序列最小值是0, 长度为2的递增子序列最小值是2,长度为3递增子序列最小值是12
因此对应的做法就是,拿到i位置后,看是否可以和当前最长子序列组成一个更长的子序列。
如果可以,将数放到长度+1位置,如果不行,看看找到一个位置使得某个子序列的最后一个值更小。
class Solution {
public int lengthOfLIS(int[] nums) {
// len表示当前递增子序列的长度
int len = 1;
int n = nums.length;
if (n == 0) {
return 0;
}
// d表示长度为i的递增子序列末尾最小值
int[] d = new int[n + 1];
d[len] = nums[0];
for (int i = 1; i < n; i++) {
// 如果当前数字大于最长递增子序列的最大值的话,代表可以组成一个更长的递增子序列
if (nums[i] > d[len]) {
len ++;
// 增加长度,并且将当前数放到增加后的位置
d[len] = nums[i];
}else {
// 判断是否有可以更新末尾最小值的长度
int left = 1;
int right = len;
// 如果pos是0的话,说明比当前所有的都小,更新第一个位置
int pos = 0;
// 因为d是单调递增的,可以用二分查找应该更新哪个位置。
while (left <= right) {
int mid = (left + right) / 2;
if (d[mid] < nums[i]) {
pos = mid;
left = mid + 1;
}else {
right = mid - 1;
}
}
d[pos + 1] = nums[i];
}
}
return len;
}
}