Given an integer array nums, return the length of the longest strictly increasing subsequence.
A subsequence is a sequence derived from an array by deleting some or no elements without changing the order of the remaining elements. For example, [3, 6, 2, 7] is a subsequence of [0, 3, 1, 6, 2, 2, 7].
The task is to find the length of the longest subsequence in which every element is greater than the previous one.
Input: nums = [10, 9, 2, 5, 3, 7, 101, 18]
Output: 4
Explanation: The longest increasing subsequence is [2, 3, 7, 101], and its length is 4.
Input: nums = [0, 1, 0, 3, 2, 3]
Output: 4
Explanation: The longest increasing subsequence is [0, 1, 2, 3], and its length is 4
Input: nums = [7, 7, 7, 7, 7, 7, 7]
A naive approach to solve this problem is to generate all the possible subsequences (using Power Set or Recursion method), check if the subsequence is increasing and then store the longest among them. Clearly, this approach will not be a useful approach for the given problem as it will end up taking O(2N), where N is the number of elements in the given array, i.e., exponential time complexity.
/*It is pseudocode and it is not tied to
any specific programming language*/
/* Function to return the length of LIS starting from
index i with the element prev_ind element refers to
the previously selected element in the subsequence*/
func(int i, int prev_ind, int arr[]) {
// Not Take case
int notTake = func(i+1, prev_ind, arr);
// Take case
int take = 0;
// If no element is chosen till now
if(prev_ind == -1)
take = func(i+1, i, arr) + 1;
/* Else the current element can be
taken if it is strictly increasing */
else if(arr[i] > arr[prev_ind])
take = func(i+1, i, arr) + 1;
// Return the maximum length obtained from both cases
return max(take, notTake);
}
#include <bits/stdc++.h>
using namespace std;
class Solution {
private:
/* Function to return the length of LIS starting from
index i with the element prev_ind element refers to
the previously selected element in the subsequence*/
int func(int i, int prevInd, vector<int> &arr) {
// base case
if(i == arr.size() - 1) {
if(prevInd == -1 || arr[prevInd] < arr[i]) return 1;
return 0;
}
// Not Take case
int notTake = func(i+1, prevInd, arr);
int take = 0; // Take case
// If no element is chosen till now
if(prevInd == -1)
take = func(i+1, i, arr) + 1;
/* Else the current element can be
taken if it is strictly increasing */
else if(arr[i] > arr[prevInd])
take = func(i+1, i, arr) + 1;
// Return the maximum length obtained from both cases
return max(take, notTake);
}
public:
/* Function to find the longest increasing
subsequence in the given array */
int LIS(vector<int>& nums) {
return func(0, -1, nums);
}
};
int main() {
vector<int> nums = {10, 9, 2, 5, 3, 7, 101, 18};
// Creating an object of Solution class
Solution sol;
int lengthOfLIS = sol.LIS(nums);
cout << "The length of the LIS for the given array is: " << lengthOfLIS << endl;
return 0;
}
class Solution {
private int func(int i, int prevInd, int[] arr) {
// base case
if (i == arr.length - 1) {
if (prevInd == -1 || arr[prevInd] < arr[i]) return 1;
return 0;
}
// Not Take case
int notTake = func(i + 1, prevInd, arr);
int take = 0; // Take case
// If no element is chosen till now
if (prevInd == -1)
take = func(i + 1, i, arr) + 1;
/* Else the current element can be
taken if it is strictly increasing */
else if (arr[i] > arr[prevInd])
take = func(i + 1, i, arr) + 1;
// Return the maximum length obtained from both cases
return Math.max(take, notTake);
}
public int LIS(int[] nums) {
return func(0, -1, nums);
}
}
class Main {
public static void main(String[] args) {
int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};
// Creating an object of Solution class
Solution sol = new Solution();
int lengthOfLIS = sol.LIS(nums);
System.out.println("The length of the LIS for the given array is: " + lengthOfLIS);
}
}
class Solution:
def func(self, i, prevInd, arr):
# base case
if i == len(arr) - 1:
if prevInd == -1 or arr[prevInd] < arr[i]:
return 1
return 0
# Not Take case
notTake = self.func(i + 1, prevInd, arr)
take = 0 # Take case
# If no element is chosen till now
if prevInd == -1:
take = self.func(i + 1, i, arr) + 1
# Else the current element can be
# taken if it is strictly increasing
elif arr[i] > arr[prevInd]:
take = self.func(i + 1, i, arr) + 1
# Return the maximum length obtained from both cases
return max(take, notTake)
def LIS(self, nums):
return self.func(0, -1, nums)
if __name__ == "__main__":
nums = [10, 9, 2, 5, 3, 7, 101, 18]
# Creating an object of Solution class
sol = Solution()
lengthOfLIS = sol.LIS(nums)
print("The length of the LIS for the given array is:", lengthOfLIS)
class Solution {
func(i, prevInd, arr) {
// base case
if (i === arr.length - 1) {
if (prevInd === -1 || arr[prevInd] < arr[i]) return 1;
return 0;
}
// Not Take case
let notTake = this.func(i + 1, prevInd, arr);
let take = 0; // Take case
// If no element is chosen till now
if (prevInd === -1)
take = this.func(i + 1, i, arr) + 1;
/* Else the current element can be
taken if it is strictly increasing */
else if (arr[i] > arr[prevInd])
take = this.func(i + 1, i, arr) + 1;
// Return the maximum length obtained from both cases
return Math.max(take, notTake);
}
LIS(nums) {
return this.func(0, -1, nums);
}
}
const nums = [10, 9, 2, 5, 3, 7, 101, 18];
// Creating an object of Solution class
const sol = new Solution();
const lengthOfLIS = sol.LIS(nums);
console.log("The length of the LIS for the given array is:", lengthOfLIS);
If you draw the recursion tree, you will see that there are overlapping subproblems. Hence the DP approache can be applied to the recursive solution to optimise it.
#include <bits/stdc++.h>
using namespace std;
class Solution {
private:
/* Function to return the length of LIS starting from
index i with the element prev_ind element refers to
the previously selected element in the subsequence*/
int func(int i, int prevInd, vector<int> &arr, vector<vector<int>> &dp) {
// base case
if(i == arr.size() - 1) {
if(prevInd == -1 || arr[prevInd] < arr[i]) return 1;
return 0;
}
// If subproblem is already calculated
if(dp[i][prevInd + 1] != -1) return dp[i][prevInd + 1];
// Not Take case
int notTake = func(i+1, prevInd, arr, dp);
int take = 0; // Take case
// If no element is chosen till now
if(prevInd == -1)
take = func(i+1, i, arr, dp) + 1;
/* Else the current element can be
taken if it is strictly increasing */
else if(arr[i] > arr[prevInd])
take = func(i+1, i, arr, dp) + 1;
// Return the maximum length obtained from both cases
return dp[i][prevInd + 1] = max(take, notTake);
}
public:
/* Function to find the longest increasing
subsequence in the given array */
int LIS(vector<int>& nums) {
int n = nums.size();
// DP array
vector<vector<int>> dp(n, vector<int>(n+1, -1));
return func(0, -1, nums, dp);
}
};
int main() {
vector<int> nums = {10, 9, 2, 5, 3, 7, 101, 18};
// Creating an object of Solution class
Solution sol;
int lengthOfLIS = sol.LIS(nums);
cout << "The length of the LIS for the given array is: " << lengthOfLIS << endl;
return 0;
}
import java.util.*;
class Solution {
/* Function to return the length of LIS starting from
index i with the element prev_ind element refers to
the previously selected element in the subsequence */
private int func(int i, int prevInd, int[] arr, int[][] dp) {
// base case
if (i == arr.length - 1) {
if (prevInd == -1 || arr[prevInd] < arr[i]) return 1;
return 0;
}
// If subproblem is already calculated
if (dp[i][prevInd + 1] != -1) return dp[i][prevInd + 1];
// Not Take case
int notTake = func(i + 1, prevInd, arr, dp);
int take = 0; // Take case
// If no element is chosen till now
if (prevInd == -1)
take = func(i + 1, i, arr, dp) + 1;
/* Else the current element can be
taken if it is strictly increasing */
else if (arr[i] > arr[prevInd])
take = func(i + 1, i, arr, dp) + 1;
// Return the maximum length obtained from both cases
dp[i][prevInd + 1] = Math.max(take, notTake);
return dp[i][prevInd + 1];
}
/* Function to find the longest increasing
subsequence in the given array */
public int LIS(int[] nums) {
int n = nums.length;
// DP array
int[][] dp = new int[n][n + 1];
for (int[] row : dp) Arrays.fill(row, -1);
return func(0, -1, nums, dp);
}
}
class Main {
public static void main(String[] args) {
int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};
// Creating an object of Solution class
Solution sol = new Solution();
int lengthOfLIS = sol.LIS(nums);
System.out.println("The length of the LIS for the given array is: " + lengthOfLIS);
}
}
class Solution:
# Function to return the length of LIS starting from
# index i with the element prev_ind element refers to
# the previously selected element in the subsequence
def func(self, i, prevInd, arr, dp):
# base case
if i == len(arr) - 1:
if prevInd == -1 or arr[prevInd] < arr[i]:
return 1
return 0
# If subproblem is already calculated
if dp[i][prevInd + 1] != -1:
return dp[i][prevInd + 1]
# Not Take case
notTake = self.func(i + 1, prevInd, arr, dp)
take = 0 # Take case
# If no element is chosen till now
if prevInd == -1:
take = self.func(i + 1, i, arr, dp) + 1
# Else the current element can be
# taken if it is strictly increasing
elif arr[i] > arr[prevInd]:
take = self.func(i + 1, i, arr, dp) + 1
# Return the maximum length obtained from both cases
dp[i][prevInd + 1] = max(take, notTake)
return dp[i][prevInd + 1]
# Function to find the longest increasing
# subsequence in the given array
def LIS(self, nums):
n = len(nums)
# DP array
dp = [[-1] * (n + 1) for _ in range(n)]
return self.func(0, -1, nums, dp)
# Main
if __name__ == "__main__":
nums = [10, 9, 2, 5, 3, 7, 101, 18]
# Creating an object of Solution class
sol = Solution()
lengthOfLIS = sol.LIS(nums)
print(f"The length of the LIS for the given array is: {lengthOfLIS}")
class Solution {
/* Function to return the length of LIS starting from
index i with the element prev_ind element refers to
the previously selected element in the subsequence */
func(i, prevInd, arr, dp) {
// base case
if (i === arr.length - 1) {
if (prevInd === -1 || arr[prevInd] < arr[i]) return 1;
return 0;
}
// If subproblem is already calculated
if (dp[i][prevInd + 1] !== -1) return dp[i][prevInd + 1];
// Not Take case
let notTake = this.func(i + 1, prevInd, arr, dp);
let take = 0; // Take case
// If no element is chosen till now
if (prevInd === -1)
take = this.func(i + 1, i, arr, dp) + 1;
/* Else the current element can be
taken if it is strictly increasing */
else if (arr[i] > arr[prevInd])
take = this.func(i + 1, i, arr, dp) + 1;
// Return the maximum length obtained from both cases
dp[i][prevInd + 1] = Math.max(take, notTake);
return dp[i][prevInd + 1];
}
/* Function to find the longest increasing
subsequence in the given array */
LIS(nums) {
const n = nums.length;
// DP array
const dp = Array.from({ length: n }, () => Array(n + 1).fill(-1));
return this.func(0, -1, nums, dp);
}
}
// Main
const nums = [10, 9, 2, 5, 3, 7, 101, 18];
// Creating an object of Solution class
const sol = new Solution();
const lengthOfLIS = sol.LIS(nums);
console.log(`The length of the LIS for the given array is: ${lengthOfLIS}`);
Pre-requisites: Lower bound
To solve the problem efficiently, a greedy approach can be used. Instead of creating new subsequences everytime, we can store the length of LIS as we process elements one by one in the array.
nums = [1, 5, 7, 2, 3]
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
/* Function to find the longest increasing
subsequence in the given array */
int LIS(vector<int>& nums) {
int n = nums.size();
vector<int> temp;
temp.push_back(nums[0]);
// Iterate on the elements of the array
for(int i=1; i < n; i++) {
// If the current element can be added to the subsequence
if(nums[i] > temp.back())
temp.push_back(nums[i]);
// Otherwise
else {
// Index at which the current element must be placed
int ind = lower_bound(temp.begin(), temp.end(), nums[i]) -
temp.begin();
// Place the current element in the subsequence
temp[ind] = nums[i];
}
}
// Return the length of temporary subsequence created so far
return temp.size();
}
};
int main() {
vector<int> nums = {10, 9, 2, 5, 3, 7, 101, 18};
// Creating an object of Solution class
Solution sol;
int lengthOfLIS = sol.LIS(nums);
cout << "The length of the LIS for the given array is: " << lengthOfLIS << endl;
return 0;
}
import java.util.*;
class Solution {
/* Function to find the longest increasing
subsequence in the given array */
public int LIS(int[] nums) {
int n = nums.length;
List<Integer> temp = new ArrayList<>();
temp.add(nums[0]);
// Iterate on the elements of the array
for (int i = 1; i < n; i++) {
// If the current element can be added to the subsequence
if (nums[i] > temp.get(temp.size() - 1))
temp.add(nums[i]);
// Otherwise
else {
// Index at which the current element must be placed
int ind = Collections.binarySearch(temp, nums[i]);
if (ind < 0) ind = -(ind + 1);
// Place the current element in the subsequence
temp.set(ind, nums[i]);
}
}
// Return the length of temporary subsequence created so far
return temp.size();
}
}
class Main {
public static void main(String[] args) {
int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};
// Creating an object of Solution class
Solution sol = new Solution();
int lengthOfLIS = sol.LIS(nums);
System.out.println("The length of the LIS for the given array is: " + lengthOfLIS);
}
}
class Solution:
# Function to find the longest increasing
# subsequence in the given array
def LIS(self, nums):
n = len(nums)
temp = []
temp.append(nums[0])
# Iterate on the elements of the array
for i in range(1, n):
# If the current element can be added to the subsequence
if nums[i] > temp[-1]:
temp.append(nums[i])
# Otherwise
else:
# Index at which the current element must be placed
ind = self.lower_bound(temp, nums[i])
# Place the current element in the subsequence
temp[ind] = nums[i]
# Return the length of temporary subsequence created so far
return len(temp)
# Binary search to find the index of the smallest element >= target
def lower_bound(self, temp, target):
left, right = 0, len(temp) - 1
while left <= right:
mid = (left + right) // 2
if temp[mid] < target:
left = mid + 1
else:
right = mid - 1
return left
# Example usage
nums = [10, 9, 2, 5, 3, 7, 101, 18]
# Creating an object of Solution class
sol = Solution()
lengthOfLIS = sol.LIS(nums)
print("The length of the LIS for the given array is:", lengthOfLIS)
class Solution {
/* Function to find the longest increasing
subsequence in the given array */
LIS(nums) {
let n = nums.length;
let temp = [];
temp.push(nums[0]);
// Iterate on the elements of the array
for (let i = 1; i < n; i++) {
// If the current element can be added to the subsequence
if (nums[i] > temp[temp.length - 1])
temp.push(nums[i]);
// Otherwise
else {
// Index at which the current element must be placed
let ind = this.lowerBound(temp, nums[i]);
// Place the current element in the subsequence
temp[ind] = nums[i];
}
}
// Return the length of temporary subsequence created so far
return temp.length;
}
// Binary search to find the index of the smallest element >= target
lowerBound(temp, target) {
let left = 0, right = temp.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (temp[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return left;
}
}
// Example usage
let nums = [10, 9, 2, 5, 3, 7, 101, 18];
// Creating an object of Solution class
let sol = new Solution();
let lengthOfLIS = sol.LIS(nums);
console.log("The length of the LIS for the given array is:", lengthOfLIS);
Time Complexity: O(N*logN), where N is the size of the given array
Since the code leverages the Lower Bound function (implemented using binary search) it results is O(N*logN) complexity.
Space Complexity: O(N), to store the elements in the temp array.
Q: Why do we use binary search in O(n log n) approach?
A: It helps efficiently find the position to replace an element in the subsequence.
Q: Can this problem be solved using recursion with memoization?
A: Yes, but it leads to O(n²) complexity, which is not optimal for large inputs.
Q: How would you solve this problem in O(n log n) using Fenwick Tree or Segment Tree?
A: Use Fenwick Tree for range maximum queries.
Q: Can we modify this problem to allow at most k decreases?
A: This becomes LIS with at most k removals, requiring graph-based DP.