赞
踩
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
证明优化子结构:
假设最长递增子序列中包含元素ak, 那么一定存在一组最优解,
它包含了a1, a2, …, ak-1 这个序列的最长递增子序列,
否则可以推出该解不是最优解
重叠子问题:
容易知道子问题重叠
定义dp[i]:从0到i的以num[i]结尾的最长上升子序列
状态转移方程:
对于 j<I ,如果num[j]<num[i] ,那么我们就可以用dp[j]来更新dp[i],在num[j]后面加上num[i],所以有:
dp[i] = max( dp[j] ) + 1, 0 < j < i 且 num[j],num[i]
dp[i] = 1, 其他情况
代码
class Solution { public int lengthOfLIS(int[] nums) { if(nums.length < 2) return nums.length; int[] dp = new int[nums.length]; dp[0] = 1; int ans = 1; for(int i = 1;i<nums.length;i++) { int cnt = 0; for(int j=0;j<i;j++) { if(nums[i]>nums[j]) cnt = Math.max(cnt,dp[j]); } dp[i] = cnt+1; ans = Math.max(ans,dp[i]); } return ans; } }
贪心策略:要让上升子序列尽可能长,就让其上升的缓慢一些,因此我们希望每次加在上升子列最后的那个数在满足条件的情况下尽可能小
定义d[i]:表示长度为 i 的最长上升子序列的末尾元素的最小值
证明d[i]单调:
如果 d[j]>=d[i] 且 j<i,我们考虑从长度为 i 的最长上升子序列的末尾删除 i-j个元素,那么这个序列长度变为 j,且第 j个元素必然小于 d[i],也就小于 d[j]。那么我们就找到了一个长度为j 的最长上升子序列,并且末尾元素比 d[j]小,从而产生了矛盾。因此数组 d[i]单调
流程:
设当前已求出的最长上升子序列的长度为 len(初始时为 1,
因为d[1] = num[0]),从前往后遍历数组num,在遍历到
num[i] 时:
如果 num[i]>d[len] ,则直接加入到 d 数组末尾,并更新 len = len+1;
否则,在 d 数组中二分查找,找到第一个比 num[i]小的数 d[k] ,并更新 d[k+1] = num[i]
示例:[ 1 , 3 , 2, 5 , 11 , 0]
第一步插入1 ,d = [1];
第二步插入3 ,d = [1,3];
第三步插入2 ,d = [1,2];
第四步插入3 ,d = [1,2,5];
第五步插入11 ,d = [1,2,5,11];
第六步插入0 ,d = [0,2,5,11];
第六步看起来可能有问题,但是其不仅不会影响返回值,并且保证了贪心策略正确性,因为新来的并不影响已有的最大长度,但是可以更新为更好(更慢增长)的组合。
代码
class Solution { public int lengthOfLIS(int[] nums) { if(nums.length < 2) return nums.length; int[] d = new int[nums.length]; int j=0; d[j] = nums[0]; for(int i=1;i<nums.length;i++) { if(nums[i]>d[j]) d[++j] = nums[i]; else { int l = 0,r=j,pos=0; boolean falg=false; int mid =(l+r)>>1; while(l<=r) { mid =(l+r)>>1; if(d[mid]<nums[i]) { falg = true; pos = mid; l = mid+1; } else r =mid-1; } if(falg) d[pos+1] = nums[i]; else d[pos] = nums[i]; } } return j+1; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。