Given an array of integers arr[], return the Longest Increasing Subsequence (LIS) of the given array. LIS is the longest subsequence where each element is strictly greater than the previous one.
If multiple LIS exist, return the one that appears first based on the lexicographical order of indices (i.e., the earliest combination of positions from the original sequence).
Examples:Â Â
Input: arr[] = [10, 20, 3, 40]
Output: [10, 20, 40]
Explanation: [10, 20, 40] is the longest subsequence where each number is greater than the previous one, maintaining the original order.Input: arr[] = [10, 22, 9, 33, 21, 50, 41, 60, 80]
Output: [10, 22, 33, 50, 60, 80]
Explanation: There are multiple longest Increasing subsequence of length 6, that is [10, 22, 33, 50, 60, 80] and [10 22 33 41 60 80]. The first one has lexicographic smallest order of indices.
Table of Content
[Naive Approach] DP with Subsequence Storage - O(n^3) Time and O(n^2) Space
The idea is to extend the bottom-up DP solution of LIS problem.
To also construct the actual LIS, we maintain an array of arrays L, where L[i] stores the longest increasing subsequence that ends at index i. Initially, L[i] contains only arr[i]. If arr[prev] < arr[i], we can extend the subsequence stored in L[prev] by adding arr[i].
For example, for arr = [3, 2, 6, 4, 5, 1]:
L[0] = [3]
L[1] = [2]
L[2] = [2, 6]
L[3] = [2, 4]
L[4] = [2, 4, 5]
L[5] = [1]Finally, we choose the longest subsequence among all L[i]. If multiple subsequences have the same length, we choose the one whose sequence of original indices is lexicographically smallest.
- Initialize lisList[i] to store the longest increasing subsequence ending at index i.
- Set lisList[0] to contain the first element of the array.
- For each index i, check all previous indices prev < i.
- If arr[i] > arr[prev] and extending lisList[prev] gives a longer subsequence, copy lisList[prev] to lisList[i].
- Append arr[i] to lisList[i] to complete the subsequence ending at index i.
- Finally, traverse all lisList entries and return the subsequence with the maximum length.
#include <iostream>
#include <vector>
using namespace std;
vector<int> getLIS(const vector<int> &arr)
{
int n = arr.size();
// lisList[i] - The longest increasing
// subsequence ending with arr[i]
vector<vector<int>> lisList;
// Initialize L[i] as empty lists
for (int i = 0; i < n; i++)
{
lisList.push_back(vector<int>());
}
// Base case: first element
lisList[0].push_back(arr[0]);
// Build the LIS lists
for (int i = 1; i < n; i++)
{
int lis = 1;
for (int prev = 0; prev < i; prev++)
{
if (arr[i] > arr[prev] && lis < (int)lisList[prev].size() + 1)
{
lisList[i] = lisList[prev];
lis = lisList[prev].size() + 1;
}
}
// add arr[i] to the longest increasing
// subsequence ending at index i
lisList[i].push_back(arr[i]);
}
vector<int> res = lisList[0];
// choose the increasing subsequence of max length
for (auto &incSubs : lisList)
{
if (incSubs.size() > res.size())
{
res = incSubs;
}
}
return res;
}
int main()
{
vector<int> arr = {10, 20, 3, 40};
vector<int> maxLis = getLIS(arr);
for (int x : maxLis)
{
cout << x << " ";
}
return 0;
}
import java.util.ArrayList;
import java.util.List;
class GFG {
static ArrayList<Integer> getLIS(int arr[])
{
int n = arr.length;
// lisList[i] - The longest increasing
// subsequence ending with arr[i]
List<List<Integer> > lisList = new ArrayList<>();
// Initialize L[i] as empty lists
for (int i = 0; i < n; i++) {
lisList.add(new ArrayList<>());
}
// Base case: first element
lisList.get(0).add(arr[0]);
// Build the LIS lists
for (int i = 1; i < n; i++) {
int lis = 1;
for (int prev = 0; prev < i; prev++) {
if (arr[i] > arr[prev]
&& lis < lisList.get(prev).size() + 1) {
lisList.set(i, new ArrayList<>(
lisList.get(prev)));
lis = lisList.get(prev).size() + 1;
}
}
// add arr[i] to the longest increasing
// subsequence ending at index i
lisList.get(i).add(arr[i]);
}
ArrayList<Integer> res
= new ArrayList<>(lisList.get(0));
// choose the increasing subsequence of max length
for (List<Integer> incSubs : lisList) {
if (incSubs.size() > res.size()) {
res = new ArrayList<>(incSubs);
}
}
return res;
}
public static void main(String[] args)
{
int[] arr = { 10, 20, 3, 40 };
ArrayList<Integer> maxLis = getLIS(arr);
for (int i = 0; i < maxLis.size(); i++) {
System.out.print(maxLis.get(i) + " ");
}
}
}
def getLIS(arr):
n = len(arr)
# lisList[i] - The longest increasing
# subsequence ending with arr[i]
lisList = []
# Initialize L[i] as empty lists
for i in range(n):
lisList.append([])
# Base case: first element
lisList[0].append(arr[0])
# Build the LIS lists
for i in range(1, n):
lis = 1
for prev in range(i):
if arr[i] > arr[prev] and lis < len(lisList[prev]) + 1:
lisList[i] = lisList[prev].copy()
lis = len(lisList[prev]) + 1
# add arr[i] to the longest increasing
# subsequence ending at index i
lisList[i].append(arr[i])
res = lisList[0].copy()
# choose the increasing subsequence of max length
for incSubs in lisList:
if len(incSubs) > len(res):
res = incSubs.copy()
return res
# Driver Code
if __name__ == "__main__":
arr = [10, 20, 3, 40]
ans = getLIS(arr)
print(*ans)
using System;
using System.Collections.Generic;
class GFG {
static List<int> getLIS(int[] arr)
{
int n = arr.Length;
// lisList[i] - The longest increasing
// subsequence ending with arr[i]
List<List<int> > lisList = new List<List<int> >();
// Initialize L[i] as empty lists
for (int i = 0; i < n; i++) {
lisList.Add(new List<int>());
}
// Base case: first element
lisList[0].Add(arr[0]);
// Build the LIS lists
for (int i = 1; i < n; i++) {
int lis = 1;
for (int prev = 0; prev < i; prev++) {
if (arr[i] > arr[prev]
&& lis < lisList[prev].Count + 1) {
lisList[i]
= new List<int>(lisList[prev]);
lis = lisList[prev].Count + 1;
}
}
// add arr[i] to the longest increasing
// subsequence ending at index i
lisList[i].Add(arr[i]);
}
List<int> res = new List<int>(lisList[0]);
// choose the increasing subsequence of max length
foreach(var incSubs in lisList)
{
if (incSubs.Count > res.Count) {
res = new List<int>(incSubs);
}
}
return res;
}
static void Main()
{
int[] arr = { 10, 20, 3, 40 };
List<int> maxLis = getLIS(arr);
foreach(int x in maxLis) { Console.Write(x + " "); }
}
}
function getLIS(arr)
{
const n = arr.length;
// lisList[i] - The longest increasing
// subsequence ending with arr[i]
let lisList = [];
// Initialize L[i] as empty lists
for (let i = 0; i < n; i++) {
lisList.push([]);
}
// Base case: first element
lisList[0].push(arr[0]);
// Build the LIS lists
for (let i = 1; i < n; i++) {
let lis = 1;
for (let prev = 0; prev < i; prev++) {
if (arr[i] > arr[prev]
&& lis < lisList[prev].length + 1) {
lisList[i] = [...lisList[prev] ];
lis = lisList[prev].length + 1;
}
}
// add arr[i] to the longest increasing
// subsequence ending at index i
lisList[i].push(arr[i]);
}
let res = [...lisList[0] ];
// choose the increasing subsequence of max length
for (let incSubs of lisList) {
if (incSubs.length > res.length) {
res = [...incSubs ];
}
}
return res;
}
// Driver code
const arr = [ 10, 20, 3, 40 ];
let ans = getLIS(arr);
console.log(...ans);
Output
10 20 40
[Better Approach] DP with Single 1D Extra Array - O(n^2) Time and O(n) Space
In the previous solution, we use an array of arrays to store all subsequences ending with every index. The idea here is to store only indexes of only the previous item in the answer LIS.
- We use an array seq[] to store indexes of previous items in LIS and used to construct the resultant sequence. Along with constructing dp[] array, we fill indexes in seq[].
- After filling seq[] and dp[], we find the index of the largest element in dp.
- Now using the index found in step 2 and seq[], we construct the result sequence.
- Initialize dp[i] as 1, representing the LIS length ending at index i, and initialize seq[i] with i.
- For every index i, check all previous indices prev < i.
- If arr[prev] < arr[i] and extending the LIS ending at prev gives a longer subsequence,
update dp[i] and set seq[i] = prev. - Find the index ansInd having the maximum value in dp[].
- Starting from ansInd, use seq[] to repeatedly move to the previous index and store the corresponding elements in the result.
- Reverse the result to obtain the LIS in the correct order.
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
vector<int> getLIS(vector<int> &arr)
{
int n = arr.size();
// Initialize dp array with 1.
vector<int> dp(n, 1);
// Initialize hash array with index values.
// We store previous indexs in LIS here
vector<int> seq(n);
for (int i = 0; i < n; i++)
{
seq[i] = i;
for (int prev = 0; prev < i; prev++)
{
// Update dp and previous values if
// condition satisfies.
if (arr[prev] < arr[i] && 1 + dp[prev] > dp[i])
{
dp[i] = 1 + dp[prev];
seq[i] = prev;
}
}
}
// Now we find the last element
// in LIS using dp[]
int ans = -1;
int ansInd = -1;
for (int i = 0; i < n; i++)
{
if (dp[i] > ans)
{
ans = dp[i];
ansInd = i;
}
}
// Construct the result sequence using seq array
vector<int> res;
res.push_back(arr[ansInd]);
while (seq[ansInd] != ansInd)
{
ansInd = seq[ansInd];
res.push_back(arr[ansInd]);
}
// Reverse the result to get the correct order
reverse(res.begin(), res.end());
return res;
}
int main()
{
vector<int> arr = {10, 20, 3, 40};
vector<int> LIS = getLIS(arr);
for (int num : LIS)
{
cout << num << " ";
}
cout << endl;
return 0;
}
import java.util.ArrayList;
import java.util.Collections;
class GFG {
static ArrayList<Integer> getLIS(int[] arr)
{
int n = arr.length;
// Initialize dp array with 1
int[] dp = new int[n];
for (int i = 0; i < n; i++) {
dp[i] = 1;
}
// Initialize seq array with index values
// (to store previous indices in LIS)
int[] seq = new int[n];
for (int i = 0; i < n; i++) {
seq[i] = i;
}
// Compute dp and seq arrays
for (int i = 0; i < n; i++) {
for (int prev = 0; prev < i; prev++) {
if (arr[prev] < arr[i]
&& 1 + dp[prev] > dp[i]) {
dp[i] = 1 + dp[prev];
seq[i] = prev;
}
}
}
// Find the index of the last element in the LIS
int ans = -1;
int ansInd = -1;
for (int i = 0; i < n; i++) {
if (dp[i] > ans) {
ans = dp[i];
ansInd = i;
}
}
// Construct the result sequence using seq array
ArrayList<Integer> res = new ArrayList<>();
res.add(arr[ansInd]);
while (seq[ansInd] != ansInd) {
ansInd = seq[ansInd];
res.add(arr[ansInd]);
}
// Reverse the result to get the correct order
Collections.reverse(res);
return res;
}
public static void main(String[] args)
{
int[] arr = { 10, 20, 3, 40 };
ArrayList<Integer> LIS = getLIS(arr);
for (int i = 0; i < LIS.size(); i++) {
System.out.print(LIS.get(i) + " ");
}
}
}
def getLIS(arr):
n = len(arr)
# Initialize dp array with 1
dp = [1] * n
# Initialize seq array with index values (to store previous indices in LIS)
seq = list(range(n))
# Compute dp and seq arrays
for i in range(n):
for prev in range(i):
if arr[prev] < arr[i] and 1 + dp[prev] > dp[i]:
dp[i] = 1 + dp[prev]
seq[i] = prev
# Find the index of the last element in the LIS
ans = -1
ans_ind = -1
for i in range(n):
if dp[i] > ans:
ans = dp[i]
ans_ind = i
# Construct the result sequence using seq array
res = []
res.append(arr[ans_ind])
while seq[ans_ind] != ans_ind:
ans_ind = seq[ans_ind]
res.append(arr[ans_ind])
# Reverse the result to get the correct order
res.reverse()
return res
# Driver Code
if __name__ == "__main__":
arr = [10, 20, 3, 40]
LIS = getLIS(arr)
print(" ".join(map(str, LIS)))
using System;
using System.Collections.Generic;
class GFG {
static List<int> getLIS(int[] arr)
{
int n = arr.Length;
// Initialize dp array with 1
int[] dp = new int[n];
for (int i = 0; i < n; i++) {
dp[i] = 1;
}
// Initialize seq array with index values (to store
// previous indices in LIS)
int[] seq = new int[n];
for (int i = 0; i < n; i++) {
seq[i] = i;
}
// Compute dp and seq arrays
for (int i = 0; i < n; i++) {
for (int prev = 0; prev < i; prev++) {
if (arr[prev] < arr[i]
&& 1 + dp[prev] > dp[i]) {
dp[i] = 1 + dp[prev];
seq[i] = prev;
}
}
}
// Find the index of the last element in the LIS
int ans = -1;
int ansInd = -1;
for (int i = 0; i < n; i++) {
if (dp[i] > ans) {
ans = dp[i];
ansInd = i;
}
}
// Construct the result sequence using seq array
List<int> res = new List<int>();
res.Add(arr[ansInd]);
while (seq[ansInd] != ansInd) {
ansInd = seq[ansInd];
res.Add(arr[ansInd]);
}
// Reverse the result to get the correct order
res.Reverse();
return res;
}
static void Main(string[] args)
{
int[] arr = { 10, 20, 3, 40 };
List<int> LIS = getLIS(arr);
Console.WriteLine(string.Join(" ", LIS));
}
}
function getLIS(arr)
{
const n = arr.length;
// Initialize dp array with 1
const dp = new Array(n).fill(1);
// Initialize seq array with index values (to store
// previous indices in LIS)
const seq = [...Array(n).keys() ];
// Compute dp and seq arrays
for (let i = 0; i < n; i++) {
for (let prev = 0; prev < i; prev++) {
if (arr[prev] < arr[i]
&& 1 + dp[prev] > dp[i]) {
dp[i] = 1 + dp[prev];
seq[i] = prev;
}
}
}
// Find the index of the last element in the LIS
let ans = -1;
let ansInd = -1;
for (let i = 0; i < n; i++) {
if (dp[i] > ans) {
ans = dp[i];
ansInd = i;
}
}
// Construct the result sequence using seq array
const res = [];
res.push(arr[ansInd]);
while (seq[ansInd] !== ansInd) {
ansInd = seq[ansInd];
res.push(arr[ansInd]);
}
// Reverse the result to get the correct order
res.reverse();
return res;
}
// Driver code
const arr = [ 10, 20, 3, 40 ];
const LIS = getLIS(arr);
console.log(LIS.join(" "));
Output
10 20 40
[Expected Approach] Using DP with Binary Search - O(n * log(n)) Time and O(n) Space
The idea is based on the LIS using binary search approach to improve the time complexity.
We process the array from right to left and use negative values to convert the increasing subsequence problem into a decreasing subsequence problem (because we are processing from right)
We maintain a
dplist and use binary search to efficiently find the correct position for each element. We also store the required indices to reconstruct the LIS with the lexicographically earliest sequence of indices.
- Initialize dp to store value-index pairs and prv to store the previous index for each element.
- Process the array from right to left and negate each value to convert the problem into a decreasing subsequence problem.
- Use binary search to find the correct position of the current value in dp.
- Update dp and store the previous index in prv for reconstruction.
- Start from the last index stored in dp and follow prv to reconstruct the LIS.
- Return the reconstructed elements in the required order.
#include <algorithm>
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
vector<int> getLIS(vector<int> &arr)
{
int N = arr.size();
vector<pair<int, int>> dp;
unordered_map<int, int> prv;
// Process array in reverse order
for (int ix = N - 1; ix >= 0; --ix)
{
int ve = -arr[ix];
// Binary search to find insertion point
auto it =
lower_bound(dp.begin(), dp.end(), make_pair(ve, 0),
[](const pair<int, int> &a, const pair<int, int> &b) { return a.first < b.first; });
int tmp = -1; // Default previous index
int i = distance(dp.begin(), it);
if (i == dp.size())
{
if (!dp.empty())
{
tmp = dp.back().second;
}
dp.emplace_back(ve, ix);
}
else
{
if (i > 0)
{
tmp = dp[i - 1].second;
}
dp[i] = {ve, ix};
}
prv[ix] = tmp;
}
// Reconstruct the LIS
vector<int> ret;
int cur = dp.back().second;
while (cur >= 0)
{
ret.push_back(arr[cur]);
cur = prv[cur];
}
return ret;
}
int main()
{
vector<int> arr = {10, 20, 3, 40};
vector<int> lis = getLIS(arr);
for (int num : lis)
{
cout << num << " ";
}
return 0;
}
import java.util.*;
class GFG {
static ArrayList<Integer> getLIS(int[] arr)
{
int N = arr.length;
ArrayList<int[]> dp = new ArrayList<>();
HashMap<Integer, Integer> prv = new HashMap<>();
// Process array in reverse order
for (int ix = N - 1; ix >= 0; --ix) {
int ve = -arr[ix];
// Binary search to find insertion point
int low = 0, high = dp.size();
while (low < high) {
int mid = low + (high - low) / 2;
if (dp.get(mid)[0] < ve)
low = mid + 1;
else
high = mid;
}
int i = low;
int tmp = -1; // Default previous index
if (i == dp.size()) {
if (!dp.isEmpty()) {
tmp = dp.get(dp.size() - 1)[1];
}
dp.add(new int[] { ve, ix });
}
else {
if (i > 0) {
tmp = dp.get(i - 1)[1];
}
dp.set(i, new int[] { ve, ix });
}
prv.put(ix, tmp);
}
// Reconstruct the LIS
ArrayList<Integer> ret = new ArrayList<>();
int cur = dp.get(dp.size() - 1)[1];
while (cur >= 0) {
ret.add(arr[cur]);
cur = prv.get(cur);
}
return ret;
}
public static void main(String[] args)
{
int[] arr = { 10, 20, 3, 40 };
ArrayList<Integer> lis = getLIS(arr);
for (int num : lis) {
System.out.print(num + " ");
}
}
}
from bisect import bisect_left
def getLIS(arr):
N = len(arr)
dp = []
prv = {}
# Process array in reverse order
for ix in range(N - 1, -1, -1):
ve = -arr[ix]
# Binary search to find insertion point
low = 0
high = len(dp)
while low < high:
mid = low + (high - low) // 2
if dp[mid][0] < ve:
low = mid + 1
else:
high = mid
i = low
tmp = -1 # Default previous index
if i == len(dp):
if dp:
tmp = dp[-1][1]
dp.append((ve, ix))
else:
if i > 0:
tmp = dp[i - 1][1]
dp[i] = (ve, ix)
prv[ix] = tmp
# Reconstruct the LIS
ret = []
cur = dp[-1][1]
while cur >= 0:
ret.append(arr[cur])
cur = prv[cur]
return ret
# Driver Code
if __name__ == "__main__":
arr = [10, 20, 3, 40]
lis = getLIS(arr)
for num in lis:
print(num, end=" ")
using System;
using System.Collections.Generic;
class GFG {
static List<int> getLIS(int[] arr)
{
int N = arr.Length;
List<(int value, int index)> dp
= new List<(int, int)>();
Dictionary<int, int> prv
= new Dictionary<int, int>();
// Process array in reverse order
for (int ix = N - 1; ix >= 0; --ix) {
int ve = -arr[ix];
// Binary search to find insertion point
int low = 0, high = dp.Count;
while (low < high) {
int mid = low + (high - low) / 2;
if (dp[mid].value < ve)
low = mid + 1;
else
high = mid;
}
int i = low;
int tmp = -1; // Default previous index
if (i == dp.Count) {
if (dp.Count > 0) {
tmp = dp[dp.Count - 1].index;
}
dp.Add((ve, ix));
}
else {
if (i > 0) {
tmp = dp[i - 1].index;
}
dp[i] = (ve, ix);
}
prv[ix] = tmp;
}
// Reconstruct the LIS
List<int> ret = new List<int>();
int cur = dp[dp.Count - 1].index;
while (cur >= 0) {
ret.Add(arr[cur]);
cur = prv[cur];
}
return ret;
}
static void Main()
{
int[] arr = { 10, 20, 3, 40 };
List<int> lis = getLIS(arr);
foreach(int num in lis)
{
Console.Write(num + " ");
}
}
}
function getLIS(arr)
{
let N = arr.length;
let dp = [];
let prv = new Map();
// Process array in reverse order
for (let ix = N - 1; ix >= 0; --ix) {
let ve = -arr[ix];
// Binary search to find insertion point
let low = 0;
let high = dp.length;
while (low < high) {
let mid = low + Math.floor((high - low) / 2);
if (dp[mid][0] < ve)
low = mid + 1;
else
high = mid;
}
let i = low;
let tmp = -1; // Default previous index
if (i === dp.length) {
if (dp.length > 0) {
tmp = dp[dp.length - 1][1];
}
dp.push([ ve, ix ]);
}
else {
if (i > 0) {
tmp = dp[i - 1][1];
}
dp[i] = [ ve, ix ];
}
prv.set(ix, tmp);
}
// Reconstruct the LIS
let ret = [];
let cur = dp[dp.length - 1][1];
while (cur >= 0) {
ret.push(arr[cur]);
cur = prv.get(cur);
}
return ret;
}
// Driver Code
let arr = [ 10, 20, 3, 40 ];
let lis = getLIS(arr);
let ans = "";
for (let num of lis) {
ans += num + " ";
}
console.log(ans.trim());
Output
10 20 40