• [LeetCode] 300. Longest Increasing Subsequence


    Given an integer array nums, return the length of the longest strictly increasing subsequence.

    A subsequence is a sequence that can be derived from an array by deleting some or no elements without changing the order of the remaining elements. For example, [3,6,2,7] is a subsequence of the array [0,3,1,6,2,2,7].

    Example 1:

    Input: nums = [10,9,2,5,3,7,101,18]
    Output: 4
    Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4.
    

    Example 2:

    Input: nums = [0,1,0,3,2,3]
    Output: 4
    

    Example 3:

    Input: nums = [7,7,7,7,7,7,7]
    Output: 1

    Constraints:

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

    Follow up: Can you come up with an algorithm that runs in O(n log(n)) time complexity?

    最长上升子序列。

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

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

    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/longest-increasing-subsequence
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    注意子序列 subsequence 和子串 substring 的不同,子序列 subsequence 是可以断开的,只要相对顺序是上升的即可。这个题有两种做法,一是动态规划,一是动态规划基础上的二分。Longest Increasing Subsequence (LIS) 也是一类常考的题型,我总结在这个tag里了。

    首先是动态规划。设 dp[i] 是以 nums[i] 为结尾的子序列的最大长度。首先初始化的时候,dp 数组的每一个元素都是 1,因为如果以当前元素为结尾,不看其他元素的话,子序列最大长度就是 1。

    更新 DP 数组的方法是用双指针,用一个指针 i 去遍历 input 的时候,同时设置另一个指针 j 扫描 j - i 的范围,如果在 j - i 的范围中有数字 nums[j] < nums[i],则 dp[i] = Math.max(dp[i], dp[j] + 1)

    最后要找的结果是 DP 数组中的最大值。动图帮助理解

    时间O(n^2)

    空间O(n)

    Java实现

     1 class Solution {
     2     public int lengthOfLIS(int[] nums) {
     3         int max = 0;
     4         int[] dp = new int[nums.length];
     5         // 初始化为1,因为每个以nums[i]结尾的子数组的长度都起码是1
     6         Arrays.fill(dp, 1);
     7         for (int i = 0; i < nums.length; i++) {
     8             for (int j = 0; j < i; j++) {
     9                 if (nums[j] < nums[i]) {
    10                     dp[i] = Math.max(dp[i], dp[j] + 1);
    11                 }
    12             }
    13             max = Math.max(max, dp[i]);
    14         }
    15         return max;
    16     }
    17 }

    DP基础上的二分法创建一个和 input 等长的 input 数组 DP,这个 DP 数组是我们维护的一个 tails 列表,其中每个元素 tails[k] 的值代表长度为 K + 1 的子序列尾部元素的值。

    把第一个数字放入 DP 数组,从第二个数字开始,用二分法去找到这个数字应该被放入的位置。这里的DP数组记录的是实打实的数字,注意最后DP数组中的结果可能并不一定是一个正确的子序列,但是长度是对的。

    遍历input,

    • 如果遇到的数字比 DP 数组里面最大的数字还要大,就加到DP数组的末端,这也是唯一扩大 DP 数组 size 的办法
    • 如果遇到的数字小于 DP 数组中的最大数字,则需要将这个数字通过二分法的方式放到 DP 数组中他该存在的位置,但是只能替换,而不是插入。
      • 举个例子,如果你现在有数组[1, 3, 5],当你遇到一个4的时候,你只有通过把5替换成4,才有可能将数组的最大值变小,从而有机会在数组的最后加入更多的值

    时间O(nlogn)

    空间O(n)

    Java实现

     1 class Solution {
     2     public int lengthOfLIS(int[] nums) {
     3         int[] dp = new int[nums.length];
     4         dp[0] = nums[0];
     5         int len = 0;
     6         for (int i = 1; i < nums.length; i++) {
     7             int pos = binarySearch(dp, len, nums[i]);
     8             if (nums[i] < dp[pos]) {
     9                 dp[pos] = nums[i];
    10             }
    11             if (pos > len) {
    12                 len = pos;
    13                 dp[len] = nums[i];
    14             }
    15         }
    16         return len + 1;
    17     }
    18     
    19     private int binarySearch(int[] dp, int len, int val) {
    20         int left = 0;
    21         int right = len;
    22         while (left + 1 < right) {
    23             int mid = left + (right - left) / 2;
    24             if (dp[mid] == val) {
    25                 return mid;
    26             } else if (dp[mid] < val) {
    27                 left = mid;
    28             } else {
    29                 right = mid;
    30             }
    31         }
    32         if (dp[right] < val) {
    33             return len + 1;
    34         } else if (dp[left] >= val) {
    35             return left;
    36         } else {
    37             return right;
    38         }
    39     }
    40 }

    LIS类相关题目

    LeetCode 题目总结

  • 相关阅读:
    用代码关闭冰刃(IceSword)
    .h和.cpp文件的区别
    获取其他进程的命令行(ReadProcessMemory其它进程的PPROCESS_PARAMETERS和PEB结构体)
    C#调用记事本并填写内容
    C#中比较两个对象的地址是否相同(也是引用计数的问题,和Java一样)
    JS代码的几个注意点规范
    javascript常用知识点集
    网站静态化处理—满足静态化的前后端分离(9)
    网站静态化处理—前后端分离—下(8)
    JS对文档进行操作
  • 原文地址:https://www.cnblogs.com/cnoodle/p/13053361.html
Copyright © 2020-2023  润新知