• Medium | LeetCode 300. 最长递增子序列 | 动态规划 | 贪心 + 二分法


    300. 最长递增子序列

    给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

    子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

    示例 1:

    输入:nums = [10,9,2,5,3,7,101,18]
    输出:4
    解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
    

    示例 2:

    输入:nums = [0,1,0,3,2,3]
    输出:4
    

    示例 3:

    输入:nums = [7,7,7,7,7,7,7]
    输出:1
    

    提示:

    • 1 <= nums.length <= 2500
    • -104 <= nums[i] <= 104

    进阶:

    • 你可以设计时间复杂度为 O(n2) 的解决方案吗?
    • 你能将算法的时间复杂度降低到 O(n log(n)) 吗?

    解题思路

    方法一: 动态规划

    定义 dp[i] 为考虑前 i 个元素,以第 ii 个数字结尾的最长上升子序列的长度,注意 nums[i] 必须被选取。

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

    [d p[i]=max (d p[j])+1, ext { 其中 } 0 leq j<i ext { 且 } operatorname{num}[j]<operatorname{num}[i] ]

    public int lengthOfLIS(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        // dp[i] 表示 前 i 个元素,以第 i 个数字结尾的最长上升子序列的长度
        int[] dp = new int[nums.length];
        dp[0] = 1;
        int maxans = 1;
        for (int i = 1; i < nums.length; i++) {
            // 初始化 当前的长度为1
            dp[i] = 1;
            // 然后遍历前面的所有元素, 看是否能够接在后面, 同时更新最大长度
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            maxans = Math.max(maxans, dp[i]);
        }
        return maxans;
    }
    

    方法二: 贪心 + 二分查找

    考虑一个简单的贪心,如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。

    基于上面的贪心思路,我们维护一个数组 d[i] ,表示长度为 ii 的最长上升子序列的末尾元素的最小值,用 len 记录目前最长上升子序列的长度,起始时len 为 1,d[1] = nums[0]。

    同时我们可以注意到d[i] 是关于 ii 单调递增的。因为如果 d[j]≥d[i] 且 j<i,我们考虑从长度为 i 的最长上升子序列的末尾删除i−j 个元素,那么这个序列长度变为 j ,且第 j 个元素 x(末尾元素)必然小于 d[i],也就小于 d[j]。那么我们就找到了一个长度为 j 的最长上升子序列,并且末尾元素比 d[j] 小,从而产生了矛盾。因此数组 d 的单调性得证。

    public int lengthOfLIS(int[] nums) {
        int len = 1, n = 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 {
                // 遇到更小的值, 就使用二分法找到d[i], 使d[i]刚好不大于当前值。
                int l = 1, r = len;
                // 如果找不到说明所有的数都比 nums[i] 大,此时要更新 d[1],所以这里将 pos 设为 0
                int 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;
    }
    
  • 相关阅读:
    回顾2011,展望我的2012
    查看MS SQL SERVER数据库中表的大小
    MS SQL SERVER数字格式化显示,每三位加逗号
    MS SQL Server 保留一行,清除多余冗余数据
    ASP.NET Webform和ASP.NET MVC的区别
    Firefox的刷新功能与Safari,IE的差距
    TIOBE如何计算编程语言的排行?
    如何让ComboBox的下拉列表宽度自适应内容的宽度
    如何启用.NET中的Fusion Log
    JavaScript的clone函数的实现及应用条件
  • 原文地址:https://www.cnblogs.com/chenrj97/p/14433529.html
Copyright © 2020-2023  润新知