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).
Table of Content
- [Naive Approach] Using Recursion - Exponential Time and Linear Space
- [Better Approach - 1] Using Memoization - O(n^2) Time and O(n) Space
- [Better Approach - 2] Using DP (Bottom Up Tabulation) - O(n^2) Time and O(n) Space
- [Expected Approach] Using Binary Search - O(n Log n) Time and O(n) Space
- Problems based on LIS
[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'
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// Returns LIS of subarray ending with index i.
int lisEndingAtIdx(vector<int>& arr, int idx) {
// Base case
if (idx == 0)
return 1;
// Consider all elements on the left of i,
// recursively compute LISs ending with
// them and consider the largest
int mx = 1;
for (int prev = 0; prev < idx; prev++)
if (arr[prev] < arr[idx])
mx = max(mx, lisEndingAtIdx(arr, prev) + 1);
return mx;
}
int lis(vector<int>& arr) {
int n = arr.size();
int res = 1;
for (int i = 1; i < n; i++)
res = max(res, lisEndingAtIdx(arr, i));
return res;
}
int main() {
vector<int> arr = {3, 10, 2, 1, 20};
cout << lis(arr);
return 0;
}
class GfG {
static int lisEndingAtIdx(int[] arr, int idx) {
// Base case
if (idx == 0)
return 1;
// Consider all elements on the left of i,
// recursively compute LISs ending with
// them and consider the largest
int mx = 1;
for (int prev = 0; prev < idx; prev++)
if (arr[prev] < arr[idx])
mx = Math.max(mx, lisEndingAtIdx(arr, prev) + 1);
return mx;
}
static int lis(int[] arr) {
int n = arr.length;
int res = 1;
for (int idx = 1; idx < n; idx++)
res = Math.max(res, lisEndingAtIdx(arr, idx));
return res;
}
public static void main(String[] args) {
int[] arr = { 3, 10, 2, 1, 20 };
System.out.println(lis(arr));
}
}
def lisEndingAtIdx(arr, idx):
# Base case
if idx == 0:
return 1
# Consider all elements on the left of i,
# recursively compute LISs ending with
# them and consider the largest
mx = 1
for prev in range(idx):
if arr[prev] < arr[idx]:
mx = max(mx, lisEndingAtIdx(arr, prev) + 1)
return mx
def lis(arr):
n = len(arr)
res = 1
for idx in range(1, n):
res = max(res, lisEndingAtIdx(arr, idx))
return res
if __name__ == "__main__":
arr = [3, 10, 2, 1, 20]
print(lis(arr))
using System;
class GfG {
static int lisEndingAtIdx(int[] arr, int idx) {
// Base case
if (idx == 0)
return 1;
// Consider all elements on the left of i,
// recursively compute LISs ending with
// them and consider the largest
int mx = 1;
for (int prev = 0; prev < idx; prev++)
if (arr[prev] < arr[idx])
mx = Math.Max(mx, lisEndingAtIdx(arr, prev) + 1);
return mx;
}
static int lis(int[] arr) {
int n = arr.Length;
int res = 1;
for (int idx = 1; idx < n; idx++)
res = Math.Max(res, lisEndingAtIdx(arr, idx));
return res;
}
static void Main(string[] args) {
int[] arr = { 3, 10, 2, 1, 20 };
Console.WriteLine(lis(arr));
}
}
function lisEndingAtIdx(arr, idx) {
// Base case
if (idx === 0)
return 1;
// Consider all elements on the left of i,
// recursively compute LISs ending with
// them and consider the largest
let mx = 1;
for (let prev = 0; prev < idx; prev++) {
if (arr[prev] < arr[idx]) {
mx = Math.max(mx,
lisEndingAtIdx(arr, prev) + 1);
}
}
return mx;
}
function lis(arr) {
let n = arr.length;
let res = 1;
for (let idx = 1; idx < n; idx++) {
res = Math.max(res, lisEndingAtIdx(arr, idx));
}
return res;
}
let arr = [ 3, 10, 2, 1, 20];
console.log(lis(arr));
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.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int lisEndingAtIdx(vector<int>& arr, int idx, vector<int>& dp) {
// Base case
if (idx == 0)
return 1;
// Check if the result is already computed
if (dp[idx] != -1)
return dp[idx];
// Consider all elements on left of i,
// recursively compute LIS ending with
// them and consider the largest
int maxLen = 1;
for (int prev = 0; prev < idx; prev++)
if (arr[prev] < arr[idx])
maxLen = max(maxLen, lisEndingAtIdx(arr, prev, dp) + 1);
// Store the result in the memo array
dp[idx] = maxLen;
return dp[idx];
}
int lis(vector<int>& arr) {
int n = arr.size();
// filling dp array with invalid value
vector<int> dp(n, -1);
int res = 1;
for (int idx = 1; idx < n; idx++)
res = max(res, lisEndingAtIdx(arr, idx, dp));
return res;
}
int main() {
vector<int> arr = {3, 10, 2, 1, 20};
cout << lis(arr);
return 0;
}
import java.util.Arrays;
class GFG {
static int lisEndingAtIdx(int[] arr, int idx, int[] dp) {
// Base case
if (idx == 0)
return 1;
// Check if the result is already computed
if (dp[idx] != -1)
return dp[idx];
// Consider all elements on left of i,
// recursively compute LIS ending with
// them and consider the largest
int maxLen = 1;
for (int prev = 0; prev < idx; prev++)
if (arr[prev] < arr[idx])
maxLen = Math.max(maxLen, lisEndingAtIdx(arr, prev, dp) + 1);
// Store the result in the memo array
dp[idx] = maxLen;
return dp[idx];
}
static int lis(int[] arr) {
int n = arr.length;
int[] dp = new int[n];
// filling dp array with invalid value
Arrays.fill(dp, -1);
int res = 1;
for (int idx = 1; idx < n; idx++)
res = Math.max(res, lisEndingAtIdx(arr, idx, dp));
return res;
}
public static void main(String[] args) {
int[] arr = {3, 10, 2, 1, 20};
System.out.println(lis(arr));
}
}
def lisEndingAtIdx(arr, idx, dp):
# Base case
if idx == 0:
return 1
# Check if the result is already computed
if dp[idx] != -1:
return dp[idx]
# Consider all elements on left of i,
# recursively compute LIS ending with
# them and consider the largest
maxLen = 1
for prev in range(idx):
if arr[prev] < arr[idx]:
maxLen = max(maxLen, lisEndingAtIdx(arr, prev, dp) + 1)
# Store the result in the memo array
dp[idx] = maxLen
return dp[idx]
def lis(arr):
n = len(arr)
# filling dp array with invalid value
dp = [-1] * n
res = 1
for idx in range(1, n):
res = max(res, lisEndingAtIdx(arr, idx, dp))
return res
if __name__ == "__main__":
arr = [3, 10, 2, 1, 20]
print(lis(arr))
using System;
class GfG {
static int lisEndingAtIdx(int[] arr, int idx, int[] dp) {
// Base case
if (idx == 0)
return 1;
// Check if the result is already computed
if (dp[idx] != -1)
return dp[idx];
// Consider all elements on left of i,
// recursively compute LIS ending with
// them and consider the largest
int maxLen = 1;
for (int prev = 0; prev < idx; prev++)
if (arr[prev] < arr[idx])
maxLen = Math.Max(maxLen, lisEndingAtIdx(arr, prev, dp) + 1);
// Store the result in the memo array
dp[idx] = maxLen;
return dp[idx];
}
static int lis(int[] arr) {
int n = arr.Length;
int[] dp = new int[n];
// filling dp array with invalid value
for (int i = 0; i < n; i++) dp[i] = -1;
int res = 1;
for (int idx = 1; idx < n; idx++)
res = Math.Max(res, lisEndingAtIdx(arr, idx, dp));
return res;
}
public static void Main() {
int[] arr = {3, 10, 2, 1, 20};
Console.WriteLine(lis(arr));
}
}
function lisEndingAtIdx(arr, idx, dp) {
// Base case
if (idx === 0)
return 1;
// Check if the result is already computed
if (dp[idx] !== -1)
return dp[idx];
// Consider all elements on left of i,
// recursively compute LIS ending with
// them and consider the largest
let maxLen = 1;
for (let prev = 0; prev < idx; prev++)
if (arr[prev] < arr[idx])
maxLen = Math.max(maxLen, lisEndingAtIdx(arr, prev, dp) + 1);
// Store the result in the memo array
dp[idx] = maxLen;
return dp[idx];
}
function lis(arr) {
let n = arr.length;
// filling dp array with invalid value
let dp = Array(n).fill(-1);
let res = 1;
for (let idx = 1; idx < n; idx++)
res = Math.max(res, lisEndingAtIdx(arr, idx, dp));
return res;
}
// Driver code
let arr = [3, 10, 2, 1, 20];
console.log(lis(arr));
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.
#include <vector>
#include <iostream>
using namespace std;
int lis(vector<int>& arr) {
int n = arr.size();
vector<int> lis(n, 1);
// Compute optimized LIS values in
// bottom-up manner
for (int i = 1; i < n; i++) {
for (int prev = 0; prev < i; prev++) {
if (arr[i] > arr[prev]) {
lis[i] = max(lis[i], lis[prev] + 1);
}
}
}
// Return maximum value in lis
return *max_element(lis.begin(), lis.end());
}
int main() {
vector<int> arr = {3, 10, 2, 1, 20};
cout << lis(arr) << endl;
return 0;
}
import java.util.Arrays;
class GFG {
static int lis(int arr[]) {
int n = arr.length;
int lis[] = new int[n];
// Initialize LIS values for all indexes
Arrays.fill(lis, 1);
// Compute optimized LIS values in
// bottom up manner
for (int i = 1; i < n; i++)
for (int prev = 0; prev < i; prev++)
if (arr[i] > arr[prev])
lis[i] = Math.max(lis[i], lis[prev] + 1);
// Pick maximum of all LIS values
int maxLen = 1;
for (int i = 0; i < n; i++)
maxLen = Math.max(maxLen, lis[i]);
return maxLen;
}
public static void main(String args[]) {
int arr[] = {3, 10, 2, 1, 20};
System.out.println(lis(arr));
}
}
def lis(arr):
n = len(arr)
# Initialize LIS values
# for all indexes
lis = [1] * n
# Compute optimized LIS
# values in bottom-up manner
for i in range(1, n):
for prev in range(0, i):
if arr[i] > arr[prev]:
lis[i] = max(lis[i], lis[prev] + 1)
# Return the maximum of all LIS values
return max(lis)
if __name__ == '__main__':
arr = [3, 10, 2, 1, 20]
print(lis(arr))
using System;
class GFG {
static int lis(int[] arr) {
int n = arr.Length;
int[] lis = new int[n];
// Initialize LIS values
// for all indexes
for (int i = 0; i < n; i++)
lis[i] = 1;
// Compute optimized LIS
// values in bottom up manner
for (int i = 1; i < n; i++) {
for (int prev = 0; prev < i; prev++) {
if(arr[i] > arr[prev])
lis[i] = Math.Max(lis[i], lis[prev] + 1);
}
}
// Pick maximum among all LIS
// lengths ending at any index
int maxLen = 0;
for (int i = 0; i < n; i++) {
maxLen = Math.Max(maxLen, lis[i]);
}
return maxLen;
}
static void Main() {
int[] arr = {3, 10, 2, 1, 20};
Console.WriteLine(lis(arr));
}
}
function lis(arr) {
let n = arr.length;
let lis = Array(n).fill(0);
let max = 0;
// Initialize LIS values for all indexes
for (let i = 0; i < n; i++)
lis[i] = 1;
// Compute optimized LIS values in
// bottom up manner
for (let i = 1; i < n; i++) {
for (let prev = 0; prev < i; prev++) {
if (arr[i] > arr[prev])
lis[i] = Math.max(lis[i], lis[prev] + 1);
}
}
let maxLen = 1;
// Pick maximum of all LIS values
for (let i = 0; i < n; i++)
maxLen = Math.max(maxLen, lis[i]);
return maxLen;
}
// Driver code
let arr = [3, 10, 2, 1, 20];
console.log(lis(arr));
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.
- First two elements: buckets = [3, 10]
- arr[2] = 2 buckets = [2, 10] -> replaces 3
- arr[3] = 1 buckets = [1, 10] -> 1 replaces 2 as it's smaller
- 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:
- Maintains the same length information
- Creates more opportunities for future elements to form longer increasing subsequences
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int lengthOfLIS(vector<int>& arr) {
int n = arr.size();
vector<int> ans;
// Initialize the answer vector with the
// first element of arr
ans.push_back(arr[0]);
for (int i = 1; i < n; i++) {
if (arr[i] > ans.back()) {
// If the current number is greater
// than the last element of the answer
// vector, it means we have found a
// longer increasing subsequence.
// Hence, we append the current number
// to the answer vector.
ans.push_back(arr[i]);
}
else {
// If the current number is not
// greater than the last element of
// the answer vector, we perform
// a binary search to find the smallest
// element in the answer vector that
// is greater than or equal to the
// current number.
// The lower_bound function returns
// an iterator pointing to the first
// element that is not less than
// the current number.
int low = lower_bound(ans.begin(), ans.end(),
arr[i])
- ans.begin();
// We update the element at the
// found position with the current number.
// By doing this, we are maintaining
// a sorted order in the answer vector.
ans[low] = arr[i];
}
}
// The length of the answer vector
// represents the length of the
// longest increasing subsequence.
return ans.size();
}
int main() {
vector<int> arr = {3, 10, 2, 1, 20};
cout << lengthOfLIS(arr) << "\n";
return 0;
}
import java.util.Arrays;
public class GFG {
static int lengthOfLIS(int[] arr) {
int n = arr.length;
int[] ans = new int[n];
int len = 0;
// Initialize the answer list with the
// first element of arr
ans[0] = arr[0];
len = 1;
for (int i = 1; i < n; i++) {
if (arr[i] > ans[len - 1]) {
// If the current number is greater
// than the last element of the answer
// list, it means we have found a
// longer increasing subsequence.
// Hence, we append the current number
// to the answer list.
ans[len] = arr[i];
len++;
} else {
// If the current number is not
// greater than the last element of
// the answer list, we perform
// a binary search to find the smallest
// element in the answer list that
// is greater than or equal to the
// current number.
// The binarySearch method returns
// the index of the first element that is not less than
// the current number.
int low = Arrays.binarySearch(ans, 0, len, arr[i]);
// We update the element at the
// found position with the current number.
// By doing this, we are maintaining
// a sorted order in the answer list.
if (low < 0) {
low = -(low + 1);
}
ans[low] = arr[i];
}
}
// The size of the answer list
// represents the length of the
// longest increasing subsequence.
return len;
}
// Driver program to test above function
public static void main(String[] args) {
int[] arr = {3, 10, 2, 1, 20};
System.out.println(lengthOfLIS(arr));
}
}
def lengthOfLIS(arr):
n = len(arr)
ans = []
# Initialize the answer list with the
# first element of arr
ans.append(arr[0])
for i in range(1, n):
if arr[i] > ans[-1]:
# If the current number is greater
# than the last element of the answer
# list, it means we have found a
# longer increasing subsequence.
# Hence, we append the current number
# to the answer list.
ans.append(arr[i])
else:
# If the current number is not
# greater than the last element of
# the answer list, we perform
# a binary search to find the smallest
# element in the answer list that
# is greater than or equal to the
# current number.
low = 0
high = len(ans) - 1
while low < high:
mid = low + (high - low) // 2
if ans[mid] < arr[i]:
low = mid + 1
else:
high = mid
# We update the element at the
# found position with the current number.
# By doing this, we are maintaining
# a sorted order in the answer list.
ans[low] = arr[i]
# The length of the answer list
# represents the length of the
# longest increasing subsequence.
return len(ans)
if __name__ == "__main__":
arr = [3, 10, 2, 1, 20]
print(lengthOfLIS(arr))
using System;
class GFG {
static int LengthOfLIS(int[] arr) {
// Binary search approach
int n = arr.Length;
int[] ans = new int[n];
int len = 0; // Tracks the current active length of the 'ans' array
// Initialize the answer list with the
// first element of arr
ans[0] = arr[0];
len = 1;
for (int i = 1; i < n; i++) {
if (arr[i] > ans[len - 1]) {
// If the current number is greater
// than the last element of the answer
// list, it means we have found a
// longer increasing subsequence.
// Hence, we append the current number
// to the answer list.
ans[len] = arr[i];
len++;
}
else {
// If the current number is not
// greater than the last element of
// the answer list, we perform
// a binary search to find the smallest
// element in the answer list that
// is greater than or equal to the
// current number.
// The BinarySearch method returns
// the index of the first element that is not less than
// the current number.
int low = Array.BinarySearch(ans, 0, len, arr[i]);
// We update the element at the
// found position with the current number.
// By doing this, we are maintaining
// a sorted order in the answer list.
if (low < 0) {
low = ~low;
}
ans[low] = arr[i];
}
}
// The count of the answer list
// represents the length of the
// longest increasing subsequence.
return len;
}
static void Main() {
int[] arr = {3, 10, 2, 1, 20};
Console.WriteLine(LengthOfLIS(arr));
}
}
function lengthOfLIS(arr) {
const n = arr.length;
const ans = [];
// Initialize the answer array with the first element of arr
ans.push(arr[0]);
for (let i = 1; i < n; i++) {
if (arr[i] > ans[ans.length - 1]) {
// If the current number is greater than the last element
// of the answer array, it means we have found a
// longer increasing subsequence. Hence, we push the current number
// to the answer array.
ans.push(arr[i]);
} else {
// If the current number is not greater than the last element of
// the answer array, we perform a binary search to find the smallest
// element in the answer array that is greater than or equal to the
// current number.
// Manual binary search to maintain O(log N) time complexity
let low = 0;
let high = ans.length - 1;
while (low < high) {
let mid = Math.floor((low + high) / 2);
if (ans[mid] < arr[i]) {
low = mid + 1;
} else {
high = mid;
}
}
// We update the element at the found position with the current number.
// By doing this, we are maintaining a sorted order in the answer array.
ans[low] = arr[i];
}
}
// The length of the answer array represents the length of the
// longest increasing subsequence.
return ans.length;
}
// Driver code
const arr = [3, 10, 2, 1, 20];
console.log(lengthOfLIS(arr));
Output
4
