Given an integer sequence, the longest ascending subsequence (LIS) is found, returning the length of the LIS. LIS (longestincreasingsubsequence)
Description
Definition of the longest ascending sub-sequence:
The longest ascending subsequence problem is finding a sequence that is as long as possible from low to high in a given sequence of unordered order, which is not necessarily sequential or unique.
The longest ascending subsequence problem, that is Longest increasing subsequence
, the abbreviation LIS
. Is the problem of finding the longest ascending sub-sequence in a sequence, which is a fairly classical problem in dynamic programming. As we can see here, this ascent is essentially a <
process of defining, so what we are solving is actually a kind of problem, that is, in a given sequence, the longest length of a sub-sequence conforming to a certain property is solved.
Sample Example
Give [5,4,1,2,3]
, LIS Yes [1,2,3]
, return3
Give [4,2,4,5,3,7]
, LIS Yes [2,4,5,7]
, return4
challenges
Requires a time complexity of O (n^2) or O (NLOGN)
labelDichotomy Lintcode All rights reserved Dynamic planning
Problem Solving, analysis:If the longest ascending subsequence can be solved, then return its length. Idea 1: You can sort all the numbers first, first find the smallest number and where it is, and then look backwards from there, if the subsequent number is greater than it, then num+1 until the end; then on the second small number, in the operation above again, If NUM is less than the first time, then keep the first time unchanged, if greater than the second number. ......... Until the end. That's a lot of complexity, ==!.
Idea 2: Dynamic solver. (Key to Master)
dp[i]
Represents the length of the LIS in a subsequence ending with I. Then I used
dp[j](0<=j<i)
To represent the length of the LIS before I. And then we can see that only when
a[i]>a[j]
, we need to decide whether to add a[i] to dp[j]. In order to ensure that each time we join are to get an optimal lis, there are two points to note: First, every time, a[i] should be added to the largest dp[j], to ensure that the local nature of the best, that is, we need to find
max(dp[j](0<=j<i))
Second, after each accession, we should update the value of Dp[j], obviously,
dp[i]=dp[j]+1
。
If we write a recursive formula, we can get
dp[i]=max(dp[j](0<=j<i))+(a[i]>a[j]?1:0)
。
so we are able to get O (n^2) dynamic programming method.
classSolution { Public: /** * @param nums:the integer array * @return: The length of LIS (longest increasing subsequence)*/ intLongestincreasingsubsequence (vector<int>nums) { //Write your code here intn=nums.size (); intDp[n]; Memset (DP,0,sizeof(DP)); intMax; for(intI=0; i<n;i++) {Max=0; for(intj=0; j<i;j++){ if(nums[i]>Nums[j]) {Max=Max (max,dp[j]); }} Dp[i]=max+1; } Max=0; for(intI=0; i<n;i++){ if(dp[i]>Max) {Max=Dp[i]; } } returnMax; }};
The above method, we spend a lot of time looking for the biggest dp[j]. If there is a way to make this dp[j] into an ascending sequence, we can use two points to optimize it, thus reducing the complexity O(nlogn)
.
Method 3: Sort the +LCS algorithm (also good, apply)
Method 4: Dynamic programming + dichotomy (highest efficiency)
transferred from:https://www.felix021.com/blog/read.php?1587
Suppose there is a sequence d[1..9] = 2 1 5 3 6 4 8 9 7, it can be seen that the LIS length is 5.
Try to find it step by step below.
We define a sequence B and then let i = 1 to 9 examine the sequence one at a.
In addition, we use a variable len to record the maximum number of times now.
First, put d[1] in order B, make b[1] = 2, that is, when there are only 11 digits 2, the smallest end of the LIS with a length of 1 is 2. Then Len=1
Then, put d[2] in an orderly place in B, so that b[1] = 1, that is, the minimum length of the LIS is 1,d[1]=2 is useless, it is easy to understand it. Then Len=1
Next, d[3] = 5,d[3]>b[1], so make b[1+1]=b[2]=d[3]=5, that is, the minimum end of the LIS with a length of 2 is 5, it is easy to understand. This time b[1..2] = 1, 5,len=2
Again, d[4] = 3, it just add to 1, 5, placed in the position of 1 is obviously inappropriate, because 1 is less than 3, the minimum length of the LIS is 1, so it is easy to infer that the length of the LIS min 1 is 2, so you can eliminate 3, this time b[1..2] = 5, 3,len = 1
Continue, d[5] = 6, it is behind 3, because b[2] = 3, and 6 is behind 3, so it is easy to infer b[3] = 6, then b[1..3] = 1, 3, 6, or is it easy to understand? Len = 3, OH.
6th, D[6] = 4, you see it between 3 and 6, so we can replace 6, get b[3] = 4. B[1..3] = 1, 3, 4, Len continues to be equal to 3
7th one, d[7] = 8, it's big, bigger than 4, uh. So b[4] = 8. Len becomes 4.
8th, D[8] = 9, get b[5] = 9, uh. Len continues to grow, to 5.
The last one, d[9] = 7, which is between b[3] = 4 and b[4] = 8, so we know that the latest b[4] =7,b[1..5] = 1, 3, 4, 7, 9,len = 5.
So we know the length of the LIS is 5.
!!!!! Attention. This 1,3,4,7,9 is not the LIS, it just stores the corresponding length to the minimum end of the LIS. With this at the end, we can insert data one at a-one place. Although the last d[9] = 7 update is not meaningful for this set of data, but if there are two numbers 8 and 9, then you can update 8 to d[5], 9 update to d[6], the length of the LIS is 6.
then you should find one thing: inserting data in B is ordered and is replaced without moving--that is, we can use a binary search to optimize the insertion time of each number to O (logn) ~~~~~ The time complexity of the algorithm is reduced to O (NLOGN)! Code:
Arr[s in non-descending sequence: E] (closed interval) find the first position greater than or equal to key, if both are less than key, return e+1int upper_bound (int arr[], int s, int e, int key) { int mid; if (Arr[e] <= key) return e + 1; while (S < e) { mid = s + (e-s)/2; if (Arr[mid] <= key) s = mid + 1; else e = mid; } return s;} int LIS (int d[], int n) { int i = 0, Len = 1, *end = (int *) alloca (sizeof (int) * (n + 1)); END[1] = d[0]; Initialization: 1 Lis end is d[0] for (i = 1; i < n; i++) { int pos = Upper_bound (end, 1, Len, d[i]);//Find insertion position End[pos] = D[i]; if (Len < POS)//update LIS length as needed len = pos; } return Len;}
Another implementation code:
classSolution { Public: /** * @param nums:the integer array * @return: The length of LIS (longest increasing subsequence)*/ intLongestincreasingsubsequence (vector<int>nums) { //Write your code here intn=nums.size (); intDp[n]; if(n==0){ return 0; } memset (DP,0,sizeof(int)*N); intlen=1; dp[0]=nums[0]; for(intI=1; i<n;i++){ intPos=lower_bound (Dp,dp+len,nums[i])-DP; Dp[pos]=Nums[i]; Len=max (len,pos+1); } returnLen; }};
In the second approach, we spend a lot of time looking for the biggest dp[j]. If there is a way to make this dp[j] into an ascending sequence, we can use two points to optimize it, thus reducing the complexity O(nlogn)
.
Fortunately, this approach does exist. We can use Dp[i] to save the largest number in the first I number, it is easy to understand that this dp[i] is already monotonous. The next process is actually some greedy ideas, for each a[i], we are in the DP array looking for the first number of the largest, it may be set to POS, and then use A[i] to update Dp[pos]. So we can see that Len should be max(len, pos+1)
.
Here we use the Lower_bound function, which returns a pointer to the first value that is less than or equal to Val, and returns the end pointer if it does not exist.
lintcode--010 (Longest ascending subsequence)