最长递增子序列 题目介绍 给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列。
示例 1:
1 2 3 输入:nums = [10,9,2,5,3,7,101,18] 输出:4 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
1 2 输入:nums = [0,1,0,3,2,3] 输出:4
示例 3:
1 2 输入:nums = [7,7,7,7,7,7,7] 输出:1
提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104
方法一:暴力搜(会超时) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int lengthOfLIS (int [] nums) { int n = nums.length; return dfs(nums, -1 , Integer.MIN_VALUE, 0 ); } int dfs (int [] nums, int i, int pre, int len) { if (i == nums.length-1 ) return len; int ans = len; for (int j = i+1 ; j < nums.length; j++) { if (i == -1 || nums[j] > nums[i]) { ans = Math.max(ans, dfs(nums, j, nums[j], len+1 )); } } return ans; } }
方法二:动态规划 定义dp[i]为考虑前i个元素,以第i个数字结尾的最长上升子序列的长度。那么状态转移方程是这样的:
1 2 3 4 5 6 7 for (int j = 0 ; j < i; j++) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[j]+1 , dp[i]); } } dp[i] = Math.max(1 , dp[i]);
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int lengthOfLIS (int [] nums) { int n = nums.length; int [] dp = new int [n]; dp[0 ] = 1 ; for (int i = 1 ; i < n; i++) { for (int j = 0 ; j < i; j++) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[j]+1 , dp[i]); } } dp[i] = Math.max(1 , dp[i]); } return Arrays.stream(dp).max().orElse(1 ); } }
方法三:贪心+二分 思路如下:
创建一个数组seq,用于存储当前的递增子序列。
遍历给定的 nums 中的每个元素num。
在数组seq中使用 lowerBound 函数找到第一个大于或等于 i 的元素的位置loc。
如果loc等于数组seq的结束位置(即没有找到大于或等于 i 的元素),则将num加入到数组seq的末尾。
否则,将数组seq中位置loc的元素替换为num,以保持数组seq仍然是递增的。
最后返回数组seq的大小,即最长递增子序列的长度。
此方法的难点在于 lowerBound 函数的实现,它和传统的二分查找还有区别。对于传统二分查找,找到元素就可以直接返回,不需要确定整个区间,但是对于 lowerBound 函数,找到相等的元素并不能直接返回,因为有可能前面还有重复的元素,所以需要把整个区间都确定了才可以。
对于区间[l,r]
(双闭区间,表示待涂色的区间),我们可以定义这样一个循环不变量:l左边的元素(不包括l)为红色,代表小于target,表示不满足要求;r右边的元素为绿色,代表大于等于target,表示满足要求。当区间所有元素的颜色都确定的时候,我们只需要返回l就可以了。
那如果没有一个元素比target大会怎么办呢?l会一直右移,直到l的值为seq.size()
,退出循环。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public int lengthOfLIS (int [] nums) { int n = nums.length; List<Integer> seq = new ArrayList <Integer>(n); for (int num : nums) { int loc = lowerBound(seq, num); if (loc == seq.size()) { seq.add(num); } else { seq.set(loc, num); } } return seq.size(); } private int lowerBound (List<Integer> seq, int target) { int l = 0 , r = seq.size()-1 ; while (l <= r) { int mid = (l+r)/2 ; if (seq.get(mid) < target) { l = mid+1 ; } else { r = mid-1 ; } } return l; } }