[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 数组的末端,len++,这也是唯一扩大 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 题目总结

posted @ 2020-06-06 08:23  CNoodle  阅读(295)  评论(0编辑  收藏  举报