LeetCode -- 300. 最长上升子序列

LeetCode -- 300. 最长上升子序列

题目描述

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

示例:

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

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

分析

一般情况下正常想到O(n2)的解法:

  1. 使用数组 cell 保存每步子问题的最优解。

  2. cell[i] 代表含第 i 个元素的最长上升子序列的长度。

  3. 求解 cell[i] 时,向前遍历找出比 i 元素小的元素 j,令 cell[i] 为max(cell[i],cell[j]+1)。
    详情可以参照官方题解。

    这个题还存在一种巧妙的解法,时间复杂度为O(NlogN) 其中使用到了二分查找。一般情况下怎么会想到和二分的关系呢?使用二分的方法需要一堆数学证明,比较复杂这里就不扯淡了。这个问题其实来自于一个Windows的游戏:
    其实最长递增子序列和一种叫做 patience game 的纸牌游戏有关,甚至有一种排序方法就叫做 patience sorting(耐心排序)。为了简单期间,后文跳过所有数学证明,通过一个简化的例子来理解一下思路。

    首先,给你一排扑克牌,我们像遍历数组那样从左到右一张一张处理这些扑克牌,最终要把这些牌分成若干堆。
    在这里插入图片描述

处理这些扑克牌要遵循以下规则:

只能把点数小的牌压到点数比它大的牌上。如果当前牌点数较大没有可以放置的堆,则新建一个堆,把这张牌放进去。如果当前牌有多个堆可供选择,则选择最左边的堆放置。

比如说上述的扑克牌最终会被分成这样 5 堆(我们认为 A 的值是最大的,而不是 1)。
在这里插入图片描述
为什么遇到多个可选择堆的时候要放到最左边的堆上呢?因为这样可以保证牌堆顶的牌有序(2, 4, 7, 8, Q),证明略。
在这里插入图片描述

按照上述规则执行,可以算出最长递增子序列,牌的堆数就是最长递增子序列的长度,证明略。
在这里插入图片描述

我们只要把处理扑克牌的过程编程写出来即可。每次处理一张扑克牌不是要找一个合适的牌堆顶来放吗,牌堆顶的牌不是有序吗,这就能用到二分查找了:用二分查找来搜索当前牌应放置的位置。

解法

   //最长上升子序列
   public static int lengthOfLIS(int[] nums) {
       if (nums.length == 0){
           return 0;
       }
       int[] re = new int[nums.length];
       int max = 1;
       Arrays.fill(re, 1);

       for (int i = 1; i < nums.length; i++) {
           int tem = 1;
           for (int j = 0; j < i; j++) {
               if (nums[i] > nums[j]){
                   tem = Math.max(tem, re[j]+1);
               }
           }
           re[i] = tem;
           max = Math.max(max, re[i]);
       }
       return max;
   }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值