zoukankan      html  css  js  c++  java
  • LeetCode题解 300 最长上升子序列

    1. 题目描述

    给定一个数字序列,求其最长上升子序列

    1.1. 测试用例

    测试用例
    int[] nums = {4,2,4,5,3,7};
    
    预期结果
    4, 序列是{2,4,5,7}
    

    1.2. 函数签名

    public int lengthOfLIS(int[] nums){
    
    }
    

    2. 题解

    2.1. 动态规划解法

    时间复杂度为O(N^2)

    2.1.1. 分析

    • 确定状态:dp[i]以nums[i]结尾的最长子序列的长度
    • 转移方程: $dp[i] = max { 1, dp[j] + 1 } quad 0 le j < i 且 nums[i] ge nums[j]$
      • 以nums[i]结尾的LIS,序列中nums[i]的上一个数字可能是nums[i]之前的任何一个比它小的数, 假设是上一个数字nums[j], 则有j < i 且 nums[i] $le$ ge nums[j], 此时的序列的长度为dp[j] + 1
      • 也有可能nums[i]之前的数字没有比它小的,那么以它结尾的LIS长度就是1
    • 返回值。注意要返回dp数组中的最大值,而不是dp[n]

    2.1.2. Java实现

    public int longestIncreasingSubsequence(int[] nums) {
        if(nums == null || nums.length == 0){
            return 0;
        }
        //dp[i] : 以nums[i]结尾的LIS的长度
        int n = nums.length;
        int[] dp = new int[n] ;
        //初始化
        Arrays.fill(dp, 1);
        for(int i = 0; i < n; i++){
            //nums[i]之前的每一个数字
            for(int j = 0; j < i; j++){
                if(nums[i] > nums[j]){
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
        }
        //dp中的最大值
        int res = dp[0];
        for (int i = 1; i < n; i++) {
            res = Math.max(res, dp[i]);
        }
        return res;
    }
    

    2.2. 二分解法

    时间复杂度为O(N*logN),单独去理解比较难,在动态规划的解法上进一步思考会更好理解

    2.2.1. 分析

    数组{4, 2, 4, 5, 2,7}, 用DP解法可以得到,dp = {1, 1, 2, 3, 2,7}。假设计算dp[3], DP解法在得到dp[3]时,需要顺序查找3之前的dp值中可用的(序列尾元素小于nums[3]的)最大值,其实可以使用二分查找。但想使用二分查找前,先要证明一些结论。

    通过dp数组我们知道dp[3]前面的dp有:

    • dp[0],dp[1]对应长度为1的序列{4}、{2},它们中最小的尾元素是2
    • dp[2]对应长度为2的序列{*, 4}, 它们中最小的尾元素是4

    结论1 : 数值相等的那些dp只需要查找最小的尾元素是否可用

    • 分析:如果尾元素大的可用,尾元素小的一定可用,但是反过来不一定,所以最后的采用的一定会是dp相同的尾元素最小的其中一个

    结论2 : dp值递增时,它们中最小的尾元素肯定也是递增的

    • 反证法,dp = len1的最小尾元素是t1,dp = len2的最小尾元素是t2, len1 < len2。如果t1 > t2 , 因为长为len2的序列为...t2, 其中肯定有长为len1的序列 ...t3...t2, t3<=t2, 和长为len1的序列的最小尾元素t1 > t2矛盾

    有了这两个结论,可以用tail[j]记录dp值为j的序列们的最小尾元素,dp[i]的值可以通过对tail进行二分查找可用的尾元素值来得到

    2.2.2. Java实现

    public int longestIncreasingSubsequence(int[] nums) {
        int n = nums.length;
        //minTail[i] 所有长度为i的子序列中最小的尾元素的值
        int[] minTail = new int[n + 1];
        minTail[0] = Integer.MIN_VALUE;
        //最后一个记录的位置
        int maxLen = 0;
        for (int i = 0; i < n; i++) {
            int prePlace = 0;
            int start = 0, end = maxLen, mid;
            while (start <= end) {
                mid = start + (end - start) / 2;
                //nums[i]会放到最后一个比它小的堆的右边
                if (nums[i] > minTail[mid]) {
                    prePlace = mid;
                    start = mid + 1;
                } else {
                    end = mid - 1;
                }
            }
            //更新nums[i]放的堆的最小值
            minTail[prePlace + 1] = nums[i];
            //所有的堆的最小值都比它小,另起一堆
            if (prePlace + 1 > maxLen) {
                maxLen = prePlace + 1;
            }
        }
        return maxLen;
    }
    
  • 相关阅读:
    强化学习的基本迭代方法
    基于文本描述的事务聚类
    学习强化学习之前需要掌握的3种技能
    其它 华硕 ASAU S4100U 系统安装 win10安装 重装系统 Invalid Partition Table 解决
    数据分析 一些基本的知识
    Python 取样式的内容 合并多个文件的样式 自定义样式
    电商 Python 生成补单公司需要的评论格式3
    SpringBlade 本地图片上传 生成缩略图
    SQL Server 字符串截取
    SpringBlade 本地图片上传
  • 原文地址:https://www.cnblogs.com/uoa7/p/13096317.html
Copyright © 2011-2022 走看看