The longest increasing subsequence problem is to find a subsequence of a given sequence in which the subsequence’s elements are in sorted order, lowest to highest, and in which the subsequence is as long as possible. This subsequence is not necessarily contiguous, or unique.

For example, consider subsequence {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}

The Longest increasing subsequence is {0, 2, 6, 9, 11, 15}

This subsequence has length 6; the input sequence has no 7-member increasing subsequences. The longest increasing subsequence in this example is not unique: for instance,

{0, 4, 6, 9, 11, 15} or

{0, 4, 6, 9, 13, 15}

are other increasing subsequences of equal length in the same input sequence.

We have already discussed a O(n^{2}) time complexity solution of LIS here which uses Dynamic Programming. In this post, a O(nlogn) time Non-DP solution is discussed.

Let S[i] be defined as the smallest integer that ends an increasing sequence of length i. Now iterate through every integer X of the input set and do the following:

- If X is more than the last element in S, then append X to the end of S. This essentially means we have found a new largest LIS.

- Otherwise find the smallest element in S, which is more than or equal to X, and replace it with X. Because S is sorted at any time, the element can be found using binary search in log(N) time.

Let’s illustrate this with the help of a example. Consider below array of integers –

{2, 6, 3, 4, 1, 2, 9, 5, 8}

Below are the steps followed by the algorithm –

Inserting 2 —- S = {2} – New largest LIS

Inserting 6 —- S = {2, 6} – New largest LIS

Inserting 3 —- S = {2, 3} – Replaced 6 with 3

Inserting 4 —- S = {2, 3, 4} – New largest LIS

Inserting 1 —- S = {1, 3, 4} – Replaced 2 with 1

Inserting 2 —- S = {1, 2, 4} – Replaced 3 with 2

Inserting 9 —- S = {1, 2, 4, 9} – New largest LIS

Inserting 5 —- S = {1, 2, 4, 5} – Replaced 9 with 5

Inserting 8 —- S = {1, 2, 4, 5, 8} – New largest LIS

So, the length of the LIS is 5 (the size of S). Please note that here S[i] is defined as the smallest integer that ends an increasing sequence of length i. Therefore, S does not represent an actual sequence but the size of S represents the length of the LIS.

Below solution uses std::set which is implemented as a red-black binary search tree which has a worst-case time complexity of O(logn) for insertion.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#include <iostream> #include <set> #include <iterator> using namespace std; // Function to find length of Longest Increasing Subsequence in given array int findLISLength(int arr[], int n) { // create an empty ordered set S. ith element in S is defined as the // smallest integer that ends an increasing sequence of length i set<int> S; // process every element one by one for (int i = 0; i < n; i++) { // insert current element into the set auto ret = S.insert(arr[i]); // get iterator to inserted element's set<int>::iterator it; if (ret.second) it = ret.first; // if element is not inserted at the end, then delete next // greater element from set if (++it != S.end()) S.erase(it); } // length of LIS is number of elements in the set return S.size(); } // main function int main() { int arr[] = { 2, 6, 3, 4, 1, 2, 9, 5, 8 }; int n = sizeof(arr) / sizeof(arr[0]); cout << "Length of LIS is " << findLISLength(arr, n); return 0; } |

`Output:`

Length of LIS is 5

**How to print LIS?**

To make things simpler, we can keep in the S, not the actual integers, but their indices in the set. That is we do not keep {1, 2, 4, 5, 8}, but keep {4, 5, 3, 7, 8} since arr[4] = 1, arr[5] = 2, arr[3] = 4, arr[7] = 5 and arr[8] = 8.

To reconstruct the actual LIS we have to use a parent array. Let parent[i] be the predecessor of element with index i in the LIS ending at element with index i. If we update properly the parent array, the actual LIS is:

arr[S[lastElementOfS]],

arr[parent[S[lastElementOfS]]],

arr[parent[parent[S[lastElementOfS]]]],

………………………………….

Below solution stores both actual integers and their indices in the set for easier implementation –

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
#include <iostream> #include <stack> #include <set> #include <map> using namespace std; // Data structure to store an element and its index in the array struct Node { int elem; int index; }; // overload compare operator for inserting into set inline bool operator<(const Node& lhs, const Node& rhs) { return lhs.elem < rhs.elem; } // Function to print LIS using parent array void print(int input[], auto parent, set<Node> S) { // container to store LIS in reverse order stack<int> lis; // start from the last element of S int index = S.rbegin()->index; // get length of LIS int n = S.size(); // retrieve LIS from parent array while (n--) { lis.push(input[index]); index = parent[index]; } // print LIS cout << "LIS is "; while(!lis.empty()) { cout << lis.top() << " "; lis.pop(); } } // Function to find Longest Increasing Subsequence in given array void printLIS(int arr[], int n) { // create an empty ordered set S (ith element in S is defined as the // smallest integer that ends an increasing sequence of length i) set<Node> S; // parent[i] will store the predecessor of element with index i in the // LIS ending at element with index i map<int, int> parent; // process every element one by one for (int i = 0; i < n; i++) { // construct node from current element and its index Node curr = {arr[i], i}; // insert current node into the set and get iterator to the // inserted node auto it = S.insert(curr).first; // if the node is not inserted at the end, then delete the next node if (++it != S.end()) S.erase(it); // get iterator to the current node and update parent it = S.find(curr); parent[i] = (--it)->index; } // print LIS using parent map print(arr, parent, S); } // main function int main() { int arr[] = { 2, 6, 3, 4, 1, 2, 9, 5, 8 }; int n = sizeof(arr) / sizeof(arr[0]); printLIS(arr, n); return 0; } |

`Output:`

LIS is 2 3 4 5 8

The time complexity of above solution is O(nlog(n)) and auxiliary space used by the program is O(n).

**References:**

**Thanks for reading.**

Please use ideone or C++ Shell or any other online compiler link to post code in comments.

Like us? Please spread the word and help us grow. Happy coding 🙂

## Leave a Reply

nice.. short and concise code

clean and concise code..

Hi,

May i know what is the behavior if i am doing a — operation to a iterator pointing to the start of the set.

For example in the above code, if the set size is 1 and if i am doing (–it)->index; what is the behavior

In your explanation above LIS of

{2,6,3,4,1,2,9,5,8} = {1,2,4,5,8}, but “4” is out of place, shouldn’t the output ={1,2,5,8}?Appun, the sequence shown doesn’t represent actual LIS but only the length of the LIS. It is already mentioned in the article –

S[i] is defined as the smallest integer that ends an increasing sequence of length i. Therefore, S does not represent an actual sequence but the size of S represents the length of the LIS.Slightly modified java version

http://ideone.com/bvpht2

Since we are usually replacing an element in the set, and sometimes increasing the size by pushing a new element onto the end, using a sorted vector (or deque) instead of a set would probably be better. It’s identical in code length and big O, but in practice is going to use less memory and will almost certainly have a faster execution time for any input.

Should be:

Otherwise will crash in case of duplicate elements in sequence.