动态规划,它来了(二)
最长递增子序列
最长递增子序列,也称为LIS,是出现非常高频的动态规划算法之一。这里对应力扣300
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
输入:nums = [0,1,0,3,2,3]
输出:4
解释:最长递增子序列是 [0,1,2,3],因此长度为 4 。
对于最长递增子序列,如果不考虑动态规划的方法,使用暴力枚举其实还是比较麻烦的,因为你不知道遇到比前面元素大的是否要递增。
比如 1 10 3 11 4 5,这个序列不能选取1 10 11而1 3 4 5才是最大的,所以暴力枚举所有情况的时间复杂度还是非常高的。
如果我们采取动态规划的方法,创建的dp[]数组,dp[i]表示以nums[i]结尾的最长递增子序列,而dp[i]的求解方式就是枚举i号前面的元素和对应结尾的最长子序列,找到一个元素值小于nums[i]并且递增序列最长,这样的时间复杂度为O(n2)。
状态转移方程为:
dp[i]=max(dp[j])+1, 其中0≤j<i且num[j]<num[i]
具体流程为:
实现代码为:
class Solution {
public int lengthOfLIS(int[] nums) {
int dp[]=new int[nums.length];
int maxLen=1;
dp[0]=1;
for(int i=1;i<nums.length;i++){
int max=0;//统计前面 末尾数字比自己小 最长递增子串
for(int j=0;j<i;j++){//枚举
//结尾数字小于当前数字 并且长度大于记录的最长
if(nums[j]<nums[i]&&dp[j]>max){
max=dp[j];
}
}
dp[i]=max+1;//前面最长 加上自己
if(maxLen<dp[i])
maxLen=dp[i];
}
return maxLen;
}
}
不过这道题还有一个优化,可以优化成O(nlogn)的时间复杂度。
我们用dp记录以 nums[i] 结尾的最长子序列长度,纵观全局,我们希望在长度一致的情况下末尾的值能够尽量的小!
例如 2,3,9,5 …… 在前面最长的长度为3 我们愿意抛弃2,3,9 而全部使用2,3,5 。也就是对于一个值,我们希望这个值能更新以它为结尾的最长的序列的末尾值。
如果这个值更新不了最长的序列,那就尝试更新第二长的末尾值以防待用。例如 2,3,9,5,4,5 这个序列2,3,5更新2,3,9;然后2,3,4更新2,3,5 为最长的2,3,4,5做铺垫。
而这个思路的核心就是维护一个lenth[]数组,length[i]表示长度为i的子序列末尾最小值,因为我们每次顺序增加一个长度说明这个值比前面的都大(做了充分比较),所以这个数组也是个递增的,递增,那么在锁定位置更新最大长度序列尾值的时候可以使用二分法优化。
实现代码为:
class Solution {
public int lengthOfLIS(int[] nums) {
int length[]=new int[nums.length];
int len=1;
length[0]=nums[0];
for(int i=1;i<nums.length;i++){
int left=0,right=len;
while (left<right){
int mid=left+(right-left)/2;
if(length[mid]<nums[i]){
left=mid+1;
}else {
right=mid;
}
}
length[left]=nums[i];
if(right==len)
len++;
}
return len;
}
}
文章转自公众号:bigsai