//给你一个整数数组 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) {// 如果数组都没有数字,就不存在递增子序列,直接返回0if (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;}}
