VOOZH about

URL: https://www.geeksforgeeks.org/dsa/longest-increasing-subsequence-dp-3/

⇱ Longest Increasing Subsequence (LIS) - GeeksforGeeks


  • Courses
  • Tutorials
  • Interview Prep

Longest Increasing Subsequence (LIS)

Last Updated : 12 Apr, 2026

Given an array arr[] of size n, find the length of the Longest Increasing Subsequence (LIS) i.e., the longest possible subsequence in which the elements of the subsequence are sorted in strictly increasing order.

Examples:

Input: arr[] = [3, 10, 2, 1, 20]
Output: 3
Explanation: The longest increasing subsequence is 3, 10, 20

Input: arr[] = [30, 20, 10]
Output:1
Explanation: The longest increasing subsequences are [30], [20] and [10]

Input: arr[] = [2, 2, 2]
Output: 1
Explanation: We consider only strictly increasing subsequences, therefore the longest increasing subsequence is [2].

Input: arr[] = [3, 4, 5, 1, 2, 3, 4]
Output: 4
Explanation: The longest strictly increasing subsequence is [1, 2, 3, 4], which gives a maximum length of 4. (Note: [3, 4, 5] is also an increasing subsequence, but its length is only 3).

[Naive Approach] Using Recursion - Exponential Time and Linear Space

The idea to do traverse the input array from left to right and find length of the Longest Increasing Subsequence (LIS) ending with every element arr[i]. Let the length found for arr[i] be L[i]. At the end we return maximum of all L[i] values. Now to compute L[i], we use recursion, we consider all smaller elements on left of arr[i], recursively compute LIS value for all the smaller elements on left, take the maximum of all and add 1 to it. If there is no smaller element on left of an element, we return 1.

Let L(i) be the length of the LIS ending at index i such that arr[i] is the last element of the LIS. Then, L(i) can be recursively written as: 

  • L(i) = 1 + max(L(prev) ) where 0 < prev < i and arr[prev] < arr[i]; or
  • L(i) = 1, if no such prev exists.

Formally, the length of LIS ending at index i, is 1 greater than the maximum of lengths of all LIS ending at some index prev such that arr[prev] < arr[i] where prev < i.

After we fill the L array, we find LIS as maximum of all in L[]

Overall LIS = max(L[i]) where 0 <= i < n

We can see that the above recurrence relation follows the optimal substructure property. Follow the below illustration to see overlapping subproblems.

Consider arr[] = [3, 10, 2, 11]

L(i): Denotes LIS of subarray ending at position 'i'

πŸ‘ Recursion Tree



Output
4

[Better Approach - 1] Using Memoization - O(n^2) Time and O(n) Space

In the recursive solution, each index tries to compute the LIS ending at that position by checking all smaller indices. However, many of these smaller-index calls overlap.
For example, while solving LIS at idx = 7, you may again need the LIS at indices 3, 5, or 6 - and these same indices might also be needed when solving LIS for other positions. This leads to repeated computation of the same subproblems.
To avoid this, we use a dp array where dp[idx] stores the LIS length ending at index idx, and reuse it whenever needed later.
This memoization ensures that each index’s LIS is computed only once, reducing the overall time complexity from exponential to quadratic.


Output
4

[Better Approach - 2] Using DP (Bottom Up Tabulation) - O(n^2) Time and O(n) Space

The idea is to maintain a 1D array lis[], where lis[i] stores the length of the longest increasing subsequence that ends at index i. Initially, each element in lis[] is set to 1, as the smallest possible subsequence for any element is the element itself.

The algorithm then iterates over each element of the array. For each element arr[i], it checks all previous elements arr[0] to arr[i-1]. If arr[i] is greater than arr[prev] (ensuring the subsequence is increasing), it updates lis[i] to the maximum of its current value or lis[prev] + 1, indicating that we can extend the subsequence ending at arr[prev] by including arr[i].

Finally, the length of the longest increasing subsequence is the maximum value in the lis[] array.

In this approach, we build the solution from smaller subproblems to larger ones. We start by assuming that the LIS ending at each index is 1 (every single element is an LIS of length 1). Then, for each index i, we look at all previous indices prev < i. If arr[prev] < arr[i], it means the subsequence ending at prev can be extended by arr[i]. So we update lis[i] using the best LIS found so far for all valid previous positions.

By the time we reach the end of the array, lis[i] already contains the correct LIS ending at i, because all smaller subproblems (earlier indices) have been computed. Finally, the overall LIS is the maximum value in the lis array.


Output
4

[Expected Approach] Using Binary Search - O(n Log n) Time and O(n) Space

The idea is to simulate the process of finding a subsequence by maintaining a list of "buckets" where each bucket represents a valid subsequence. Initially, start with an empty list and iterate through the input vector arr from left to right.

For each number in arr, perform the following steps:

  • If the number is greater than the last element of the last bucket (i.e., the largest element in the current subsequence), we append the number to the end of the list. This indicates that we have found a longer subsequence.
  • Otherwise, perform a binary search on the list of buckets to find the smallest element that is greater than or equal to the current number. This step helps maintain the property of increasing elements in the buckets.
  • Once find the position to update, replace that element with the current number. This keeps the buckets sorted and ensures that we have the potential for a longer subsequence in the future.

Note: The resulting array only stores the length of longest increasing subsequence, and not the actual subsequence. Go through the illustration to clear this doubt.

Illustration:

Example: arr = [3, 10, 2, 1, 20], why keeping 1 (the smallest value) helps?

Use binary search to find the position where new element is to be inserted.

  1. First two elements: buckets = [3, 10]
  2. arr[2] = 2 buckets = [2, 10] -> replaces 3
  3. arr[3] = 1 buckets = [1, 10] -> 1 replaces 2 as it's smaller
  4. arr[4] = 20 buckets = [1, 10, 20] -> 20 is appended as it's larger

This shows that by replacing 3 with 1, we created the opportunity to find the subsequence [1, 10, 20], which is longer than our initial [3, 10]. If we had kept [3, 10], we wouldn't have been able to add 2 to our sequence!

The key insight is that keeping smaller values at each position:

  1. Maintains the same length information
  2. Creates more opportunities for future elements to form longer increasing subsequences

Output
4

Problems based on LIS

Comment