O(nlgn)-time algorithm to find the LIS
In the spirit of dynamic programming, we first consider what information we would need from previous problems to solve for a longest increasing sub-sequence in A[n]
. There are two cases:
Case 1. The length of the LIS(s) of A[n] is equal to the length of the LIS(s) of A[n-1].
This will occur when for all A[k], where A[k] is the last element in a longest increasing sub-sequence
of A[n-1], A[n] <= A[k].
Case 2. The length of the LIS(s) of A[n] is equal to 1 + the length of the LIS(s) of A[n-1].
This will occur when A[n] > than some A[k], where A[k] is the last element in a longest increasing
sub-sequence of A[n-1].
Clearly Case (2) is desired, since we are increasing the length of the LIS. So what differentiates Cases (1) and (2)? Well, note that to be in Case (1), A[n]
must be less than ALL of some elements A[k]
, whereas to be in Case (2), A[n]
must simply be greater than ONE instance of A[k]
. To maximize the chances of the latter case, then, we would want to keep track of the minimum A[k]
that yields a longest sub-sequence of some number of elements.
To get the longest sub-sequence itself, we must also have a way to keep track of which elements are part of our current chain, since we are changing values. We do this by maintaining a new array that holds the previous element in the longest chain starting with the element that we’re using. This will be clearer in an example below.
Summary:
In increasing indexing order though the array, compare a new element to the last element in the current
LIS
. If the new element is larger, we attach to theLIS
. Update the array for previous values here. This runs inO(n)
time.If the new element is not larger, we find the smallest element in the
LIS
sequence that is larger than our value and replace it with our current value. We can find this element using binary search, since the values index byI[ ]
are in ascending order. This runs inO(log(n))
time.Trace back through the previous-index-array to recover the actual sub-sequence. This runs in
O(n)
time.
Example:
A = [2, 4, 1, 8, 9, 5, 6, 7]
I[k] holds the index of the last element in some increasing sub-sequence(not longest increasing sub-sequence) of k values. And A[I[0]] < A[I[1]] < … < A[I[len(I) - 1]].
P[k] holds the index of the previous value in the longest increasing sub-sequence of k values.
2: I[ ] = {0}, P[ ] = {} LIS is {2} 4: I[ ] = {0, 1}, P[ ] = {0} LIS is {2, 4} 1: I[ ] = {2, 1}, P[ ] = {0} LIS is {2, 4} 8: I[ ] = {2, 1, 3}, P[ ] = {0, 1} LIS is {2, 4, 8} 9: I[ ] = {2, 1, 3, 4}, P[ ] = {0, 1, 3} LIS is {2, 4, 8, 9} 5: I[ ] = {2, 1, 5, 4}, P[ ] = {0, 1, 3} LIS is {2, 4, 8, 9} 6: I[ ] = {2, 1, 5, 6} P[ ] = {0, 1, 5} LIS is {2, 4, 5, 6} 7: I[ ] = {3, 2, 5, 6, 7} P[ ] = {0, 1, 5, 6} LIS is {2, 4, 5, 6, 7}
Pseudo Code:
1 | //Input: An array A |