[LeetCode] 300. Longest Increasing Subsequence 最长递增子序列
Given an unsorted array of integers, find the length of longest increasing subsequence.
For example,
Given [10, 9, 2, 5, 3, 7, 101, 18]
,
The longest increasing subsequence is [2, 3, 7, 101]
, therefore the length is 4
. Note that there may be more than one LIS combination, it is only necessary for you to return the length.
Your algorithm should run in O(n2) complexity.
Follow up: Could you improve it to O(n log n) time complexity?
解法1: 动态规划DP,类似brute force的解法,维护一个一维dp数组,其中dp[i]表示以nums[i]为结尾的最长递增子串的长度。Time: O(n^2)
设长度为N的数组为{a0,a1, a2, ...an-1),则假定以aj结尾的数组序列的最长递增子序列长度为L(j),则L(j)={ max(L(i))+1, i<j且a[i]<a[j] }。也就是说,需要遍历在j之前的所有位置i(从0到j-1),找出满足条件a[i]<a[j]的L(i),求出max(L(i))+1即为L(j)的值。最后,我们遍历所有的L(j)(从0到N-1),找出最大值即为最大递增子序列。时间复杂度为O(N^2)。
例如给定的数组为{5,6,7,1,2,8},则L(0)=1, L(1)=2, L(2)=3, L(3)=1, L(4)=2, L(5)=4。所以该数组最长递增子序列长度为4,序列为{5,6,7,8}。
解法2: 二分查找法Binary Search, 维护一个单调递增子序列,如果当前值小于单调递增子序列中的某个元素,则替换之,因为单调递增子序列能否增长,值取决于最后一个元素,替换内部的元素并不影响。Time: O(nlogn)
假设存在一个序列d[1..9] = { 2,1 ,5 ,3 ,6,4, 8 ,9, 7},可以看出来它的LIS长度为5。
定义一个序列B,然后让 i = 1 to 9 逐个考察这个序列。用一个变量Len来记录现在最长LIS长度。
首先,把d[1]有序地放到B里,令B[1] = 2,就是说当只有1一个数字2的时候,长度为1的LIS的最小末尾是2。这时Len=1
然后,把d[2]有序地放到B里,令B[1] = 1,就是说长度为1的LIS的最小末尾是1,d[1]=2已经没用了,很容易理解吧。这时Len=1
接着,d[3] = 5,d[3]>B[1],所以令B[1+1]=B[2]=d[3]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候B[1..2] = 1, 5,Len=2
再来,d[4] = 3,它正好加在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候B[1..2] = 1, 3,Len = 2
继续,d[5] = 6,它在3后面,因为B[2] = 3, 而6在3后面,于是很容易可以推知B[3] = 6, 这时B[1..3] = 1, 3, 6,还是很容易理解吧? Len = 3 了噢。
第6个, d[6] = 4,你看它在3和6之间,于是我们就可以把6替换掉,得到B[3] = 4。B[1..3] = 1, 3, 4, Len继续等于3
第7个, d[7] = 8,它很大,比4大,嗯。于是B[4] = 8。Len变成4了
第8个, d[8] = 9,得到B[5] = 9,嗯。Len继续增大,到5了。
最后一个, d[9] = 7,它在B[3] = 4和B[4] = 8之间,所以我们知道,最新的B[4] =7,B[1..5] = 1, 3, 4, 7, 9,Len = 5。
于是我们知道了LIS的长度为5。
注意,这个1,3,4,7,9不是LIS,它只是存储的对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。虽然最后一个d[9] = 7更新进去对于这组数据没有什么意义,但是如果后面再出现两个数字 8 和 9,那么就可以把8更新到d[5], 9更新到d[6],得出LIS的长度为6。
然后发现一件事情:在B中插入数据是有序的,而且是进行替换而不需要挪动——也就是说,可以使用二分查找,将每一个数字的插入时间优化到O(logN)~~~~~于是算法的时间复杂度就降低到了O(NlogN)~!
Java: DP
public class Solution { public int lengthOfLIS(int[] nums) { if (nums == null || nums.length == 0) return 0; int max = 1; int[] lens = new int[nums.length]; Arrays.fill(lens, 1); for(int i=1; i<nums.length; i++) { for(int j=0; j<i; j++) { if (nums[j]<nums[i]) lens[i] = Math.max(lens[i], lens[j]+1); } max = Math.max(max, lens[i]); } return max; } }
Java: BS
public class Solution { public int lengthOfLIS(int[] nums) { int[] increasing = new int[nums.length]; int size = 0; for(int i=0; i<nums.length; i++) { int left=0, right=size-1; while (left<=right) { int m=(left+right)/2; if (nums[i] > increasing[m]) left = m + 1; else right = m - 1; } increasing[left] = nums[i]; if (left==size) size ++; } return size; } }
Java: TreeSet
public class Solution { public int lengthOfLIS(int[] nums) { if (nums == null || nums.length == 0) return 0; int max = 1; TreeSet<Integer> ts = new TreeSet<>(new Comparator<Integer>() { @Override public int compare(Integer i1, Integer i2) { return Integer.compare(nums[i1], nums[i2]); } }); int[] lens = new int[nums.length]; Arrays.fill(lens, 1); for(int i=0; i<nums.length; i++) { if (ts.contains(i)) ts.remove(i); ts.add(i); Set<Integer> heads = ts.headSet(i); for(int head: heads) { lens[i] = Math.max(lens[i], lens[head] + 1); } max = Math.max(max, lens[i]); } return max; } }
Python: BS, T: O(nlogn), S: O(n)
class Solution(object): def lengthOfLIS(self, nums): LIS = [] def insert(target): left, right = 0, len(LIS) - 1 # Find the first index "left" which satisfies LIS[left] >= target while left <= right: mid = left + (right - left) / 2 if LIS[mid] >= target: right = mid - 1 else: left = mid + 1 # If not found, append the target. if left == len(LIS): LIS.append(target); else: LIS[left] = target for num in nums: insert(num) return len(LIS)
Python: DP, T: O(n^2), S: O(n)
class Solution(object): def lengthOfLIS(self, nums): dp = [] # dp[i]: the length of LIS ends with nums[i] for i in xrange(len(nums)): dp.append(1) for j in xrange(i): if nums[j] < nums[i]: dp[i] = max(dp[i], dp[j] + 1) return max(dp) if dp else 0
Python: wo
class Solution(object): def lengthOfLIS(self, nums): """ :type nums: List[int] :rtype: int """ if not nums: return 0 n = len(nums) dp = [1] * n max_len = 1 for i in xrange(n): for j in xrange(i): if nums[i] > nums[j]: dp[i] = max(dp[i], dp[j] + 1) max_len = max(max_len, dp[i]) return max_len
C++:DP
class Solution { public: int lengthOfLIS(vector<int>& nums) { if (nums.size() == 0) return 0; vector<int> dp(nums.size(), 1); int res = 1; for (int i = 1; i < nums.size(); ++i){ for (int j = 0; j < i; ++j){ if (nums[j] < nums[i]){ dp[i] = max(dp[i], 1+dp[j]); } } res = max(res, dp[i]); } return res; } };
C++:BS
class Solution { public: int lengthOfLIS(vector<int>& nums) { if (nums.empty()) return 0; vector<int> ends{nums[0]}; for (auto a : nums) { if (a < ends[0]) ends[0] = a; else if (a > ends.back()) ends.push_back(a); else { int left = 0, right = ends.size(); while (left < right) { int mid = left + (right - left) / 2; if (ends[mid] < a) left = mid + 1; else right = mid; } ends[right] = a; } } return ends.size(); } };
C++:BS
class Solution { public: int lengthOfLIS(vector<int>& nums) { vector<int> dp; for (int i = 0; i < nums.size(); ++i){ int lo = 0, hi = dp.size(); while (lo < hi){ int mi = (lo + hi)/2; if (dp[mi] < nums[i]) lo = mi + 1; else hi = mi; } if (hi == dp.size()) dp.push_back(nums[i]); else dp[hi] = nums[i]; } return dp.size(); } };
类似题目:
[LeetCode] 354. Russian Doll Envelopes 俄罗斯套娃信封
[LeetCode] 673. Number of Longest Increasing Subsequence 最长递增序列的个数
[LeetCode] 674. Longest Continuous Increasing Subsequence 最长连续递增序列
All LeetCode Questions List 题目汇总