Loading

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, \text { 其中 } 0 \leq j<i \text { 且 } \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;
}
posted @ 2021-02-22 23:15  反身而诚、  阅读(128)  评论(0编辑  收藏  举报