• 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;
    }
    
  • 相关阅读:
    数据类型装换
    变量及数据类型
    27 网络通信协议 udp tcp
    26 socket简单操作
    26 socket简单操作
    14 内置函数 递归 二分法查找
    15 装饰器 开闭原则 代参装饰器 多个装饰器同一函数应用
    12 生成器和生成器函数以及各种推导式
    13 内置函数 匿名函数 eval,exec,compile
    10 函数进阶 动态传参 作用域和名称空间 函数的嵌套 全局变量
  • 原文地址:https://www.cnblogs.com/chenrj97/p/14433529.html
Copyright © 2020-2023  润新知