最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。

方法一:动态规划

思路与算法:
定义 dp[i]dp[i] 为考虑前 ii 个元素,以第 ii 个数字结尾的最长上升子序列的长度,注意 \textit{nums}[i]nums[i] 必须被选取。

我们从小到大计算 dp[]dp[] 数组的值,在计算 dp[i]dp[i] 之前,我们已经计算出 dp[0 \ldots i-1]dp[0…i−1] 的值,则状态转移方程为:

dp[i]=max(dp[j])+1,其中0≤j<i且num[j]<num[i]

即考虑往 dp[0 … i-1]dp[0…i−1] 中最长的上升子序列后面再加一个nums[i]。由于 dp[j]dp[j] 代表 \nums[0…j] 中以 nums[j] 结尾的最长上升子序列,所以如果能从 dp[j]dp[j] 这个状态转移过来,那么 nums[i] 必然要大于 nums[j],才能将nums[i] 放在 nums[j] 后面以形成更长的上升子序列。

最后,整个数组的最长上升子序列即所有 dp[i]dp[i] 中的最大值。

官方的解释总是这么恶心 但是把题领悟了之后再去看官方解释也能看得明白 直接上代码

public class 最长上升子序列 {
    public int lengthOfLIS(int[] nums){
        if(nums.length==0){
            return 0;//长度为0即没有
        }
        int[] dp=new int[nums.length];//创建dp数组与nums数组相同大小
        dp[0]=1;
        int maxans=1;
        for(int i=1;i<dp.length;i++){//这里的i从1开始  因为是从第二个元素与之前元素相比较
            int maxval=0;//这里的maxval用来记录每次外循环结束后的最大长度子序列  即i之前的最大长度上升子序列
            for(int j=0;j<i;j++){//  j 就是从0开始遍历与i比较
                if(nums[i]>nums[j]){//如果nums[i]>nums[j]
                    maxval=Math.max(dp[j],maxval);//这里更新maxval的值 取最大
                }
            }
            dp[i]=maxval+1;
            maxans=Math.max(maxans,dp[i]);
        }
        return maxans;
    }
}

1.maxval的作用是找到0…i-1元素中 比num[i]大的元素的最长上升子序列的长度
2.不管里层循环如何, 最后dp[i]都等于maxval+1 ,因为当存在比num[i]大的元素时我们就得获取这些元素的最大上升子序列值 , 当不存在时,那么就是0+1=1
3.最后更新maxans的值,看maxans与dp[i]的值大小 ,最终maxans的值就是最长上升子序列的长度

方法二:贪心+二分算法
public class 最长上升子序列2 {
    public static int lengthOfLIS(int[] nums){
        int len = 1, n = (int)nums.length;
        if (n == 0) return 0;
        int[] d=new int[n+1];
        d[len] = nums[0];
        for (int i = 1; i < n; ++i) {
            if (nums[i] > d[len]) d[++len] = nums[i];
            else{
                int l = 1, r = len, pos = 0; // 如果找不到说明所有的数都比 nums[i] 大,此时要更新 d[1],所以这里将 pos 设为 0
                while (l <= r) {
                    int mid = (l + r) >> 1;
                    if (d[mid] < nums[i]) {
                        pos = mid;
                        l = mid + 1;
                    }
                    else r = mid - 1;
                }
                d[pos + 1] = nums[i];
            }
        }
        return len;
    }
}

这里会产生几个问题 ,只是想到用一个判断和条件不符合时用二分法找位置,确实不难 ,但是有两个问题
1.为什么当条件不符合时就要去d数组里找到大于等于该数的最小值并将其替换?
2.为什么最后得到的d数组的长度就是最长上升子序列的长度?

解答:1、比如 3 5 6 2 4 这个序列我们可以把所有的上升序列写出来 (上升序列 单个字符也是上升序列)
单个字符的序列有:“3” “5” “6” “2” “4” 结尾最小的序列是”2“ 结尾数为2
双个字符的序列有:“3,5” “5,6” “2,4” 结尾最小的序列是”2,4“ 结尾数为4
三个字符的序列有:“3,5,6” 结尾最小的序列是”3,5,6“ 结尾数为6

d数组为【2,4,6】
此时我们发现出现一个比6大的数时所有上升序列的 长度都会加一, 但是出现比6小但是比2大的数时有的序列长度加一有的不加,出现5时,就要把6替换 ,d数组就为【2,4,5】,这时是因为2,4,5 和3,5,6都是三字符上升序列,但是将6换成5是为了之后更容易碰到比该数大的数,即碰到比6大的数的概率永远比碰到比5大的数概率小我们就会更容易得到最长字串

2、我们发现d数组中的每一个字符都是该字符(下标加一)个字符的最长上升子序列的结尾最小值
即2为(0+1)个字符上升序列结尾最小值 4为(1+1)个字符上升序列最小值 6为(2+1)个字符上升序列最小值
所以d数组长度就为最长上升子序列的长度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值