Longest Increasing Subsequence in C++
Last Updated :
24 Jul, 2024
In this article, we will learn the concept of the Longest Increasing Subsequence (LIS) in a given sequence using C++ language. The LIS problem is about finding the longest subsequence of a sequence in which the elements are in sorted order, from lowest to highest, and not necessarily contiguous.
Example:
Input:
int arr[] = { 3, 10, 2, 1, 20 };
Output:
Length of the longest increasing subsequence is: 3
Longest Increasing Subsequence in C++Below, we have discussed the multiple approaches to find the Longest Increasing Subsequence from a given array in C++.
In this approach, we will explore all the possible subsequences to find the longest increasing one by comparing each element with previous elements to check if it can be included in the increasing sequence or not.
Approach:
- Define a function lisRecursive that takes the array arr and its length n.
- Base Case: If the length is 1, return 1.
- For each element, recursively compute the LIS ending at each index.
- Return the maximum length found.
Below is the implementation of the above recursive approach in C++:
C++
// C++ program to find the length of the longest increasing
// subsequence (LIS) using a recursive approach
#include <iostream>
using namespace std;
// Function to return the maximum of two integers
int max(int a, int b) { return (a > b) ? a : b; }
// Recursive function to calculate LIS ending at arr[n-1]
int lisRecursive(int arr[], int n, int* max_ref)
{
// Base case: single element is an increasing
// subsequence of length 1
if (n == 1)
return 1;
// Initialize the max ending length
int res, max_ending_here = 1;
// Loop through all elements before arr[n-1]
for (int i = 1; i < n; i++) {
// Recursively calculate LIS ending at arr[i-1]
res = lisRecursive(arr, i, max_ref);
// Update max_ending_here if arr[i-1] is less than
// arr[n-1] and results in a longer subsequence
if (arr[i - 1] < arr[n - 1]
&& res + 1 > max_ending_here)
max_ending_here = res + 1;
}
// Update the overall maximum length of LIS found so far
if (*max_ref < max_ending_here)
*max_ref = max_ending_here;
// Return length of LIS ending at arr[n-1]
return max_ending_here;
}
// Function to calculate the length of the longest
// increasing subsequence
int lis(int arr[], int n)
{
// Initialize maximum length of LIS
int max = 1;
// Call recursive function
lisRecursive(arr, n, &max);
// Return the maximum length of LIS
return max;
}
int main()
{
// Initialize array and find its size
int arr[] = { 3, 10, 2, 1, 20 };
int n = sizeof(arr) / sizeof(arr[0]);
// Print the length of the longest increasing
// subsequence
cout << "Length of LIS is " << lis(arr, n) << endl;
return 0;
}
Time Complexity: O(2^n), due to overlapping subproblems.
Auxiliary Space: O(1), excluding internal stack space.
We can use the memoization technique to improve the recursive approach by storing the results of subproblems to avoid redundant calculations.
Approach:
- Create a memoization array memo to store the LIS for each index.
- Define a function lisMemoization that takes arr, n, and memo.
- Base Case: If the length is 1, return 1.
- If the result is already computed, return it.
- Recursively compute and store results for the remaining elements.
- Return the computed result from the memoization array.
Below is the implementation of the above memoization technique in C++:
C++
// C++ program to find the length of the longest increasing
// subsequence (LIS) using memoization
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
// Function to return the maximum of two integers
int max(int a, int b) { return (a > b) ? a : b; }
// Recursive function to calculate LIS ending at arr[n-1]
// using memoization
int lisMemoization(int arr[], int n, int* memo)
{
// Check if result is already computed
if (memo[n] != -1)
return memo[n];
// Initialize the max ending length
int res, max_ending_here = 1;
// Loop through all elements before arr[n-1]
for (int i = 1; i < n; i++) {
// Recursively calculate LIS ending at arr[i-1] and
// update max_ending_here
res = lisMemoization(arr, i, memo);
if (arr[i - 1] < arr[n - 1]
&& res + 1 > max_ending_here)
max_ending_here = res + 1;
}
// Store the computed value in memo array and return it
return memo[n] = max_ending_here;
}
// Function to calculate the length of the longest
// increasing subsequence
int lis(int arr[], int n)
{
// Initialize memoization vector with -1
vector<int> memo(n + 1, -1);
// Initialize maximum length of LIS
int maxLength = 1;
// Calculate LIS for each element and update maxLength
for (int i = 1; i <= n; i++)
maxLength = max(
maxLength, lisMemoization(arr, i, memo.data()));
// Return the maximum length of LIS
return maxLength;
}
int main()
{
// Initialize array and find its size
int arr[] = { 3, 10, 2, 1, 20 };
int n = sizeof(arr) / sizeof(arr[0]);
// Print the length of the longest increasing
// subsequence
cout << "Length of LIS is " << lis(arr, n) << endl;
return 0;
}
Time Complexity: O(n^2), due to nested loop.
Auxiliary Space: O(n), for memoization array.
In this method, we will use the concept of dynamic programming to iteratively computes the LIS by storing intermediate results, making the process more efficient.
Approach:
- Create an array lis of size n and initialize all values to 1.
- For each element, find the longest subsequence ending at that element.
- Return the maximum value in the lis array, which will be the length of the LIS.
Below is the implementation of the above dynamic programming approach in C++:
C++
// C++ program to find the length of the longest increasing
// subsequence (LIS) using tabulation
#include <iostream>
#include <vector>
using namespace std;
// Function to return the maximum of two integers
int max(int a, int b) { return (a > b) ? a : b; }
// Function to calculate the length of the longest
// increasing subsequence using tabulation
int lisTabulation(int arr[], int n)
{
// Initialize a vector to store the LIS values for each
// element
vector<int> lis(n, 1);
// Loop through each element starting from the second
for (int i = 1; i < n; i++) {
// For each element, compare it with all previous
// elements
for (int j = 0; j < i; j++) {
// If the current element is greater and the LIS
// value can be increased
if (arr[i] > arr[j] && lis[i] < lis[j] + 1)
lis[i] = lis[j] + 1;
}
}
// Initialize the variable to store the maximum length
// of LIS
int maxLength = 0;
// Find the maximum value in the lis vector
for (int i = 0; i < n; i++)
maxLength = max(maxLength, lis[i]);
return maxLength; // Return the maximum length of LIS
}
int main()
{
// Initialize array and find its size
int arr[] = { 3, 10, 2, 1, 20 };
int n = sizeof(arr) / sizeof(arr[0]);
// Print the length of the longest increasing
// subsequence
cout << "Length of LIS is " << lisTabulation(arr, n)
<< endl;
return 0;
}
Time Complexity: O(n^2)
Auxiliary Space: O(n), for storing LIS values at each index.
We can also, use the binary search to improve time complexity by efficiently finding the position where the current element should be placed in the subsequence.
Approach:
- Create an array tail to store the smallest ending element of all increasing subsequences of length i+1.
- Use binary search to find the position of the current element in tail.
- If the element is larger than the largest element in tail, extend tail. Otherwise, replace the existing element in tail.
- Return the length of the tail array, which is the length of the LIS.
Below is the implementation of the above binary search approach in C++:
C++
// C++ program to find the length of the longest increasing
// subsequence (LIS) using binary search
#include <iostream>
#include <vector>
using namespace std;
// Function to perform binary search on the tail array
int binarySearch(vector<int>& tail, int l, int r, int key)
{
// Loop until the search space is reduced to one element
while (r - l > 1) {
// Find the middle element
int m = l + (r - l) / 2;
// Update the search space based on the comparison
if (tail[m] >= key)
r = m;
else
l = m;
}
// Return the position to insert the key
return r;
}
// Function to calculate the length of the longest
// increasing subsequence using binary search
int lisBinarySearch(int arr[], int n)
{
// Check if the array is empty
if (n == 0)
return 0;
// Initialize the tail array to store the smallest
// ending element of each length subsequence
vector<int> tail(n);
// Initialize the length of the LIS
int length = 1;
// The first element is the initial subsequence
tail[0] = arr[0];
// Loop through the array elements
for (int i = 1; i < n; i++) {
// If the current element is smaller than the first
// element in tail
if (arr[i] < tail[0])
tail[0] = arr[i];
// If the current element is greater than the last
// element in tail
else if (arr[i] > tail[length - 1])
tail[length++] = arr[i];
// If the current element can replace an element in
// the middle
else
tail[binarySearch(tail, -1, length - 1, arr[i])]
= arr[i];
}
// Return the length of the LIS
return length;
}
int main()
{
// Initialize the array and find its size
int arr[] = { 3, 10, 2, 1, 20 };
int n = sizeof(arr) / sizeof(arr[0]);
// Print the length of the longest increasing
// subsequence
cout << "Length of LIS is " << lisBinarySearch(arr, n)
<< endl;
return 0;
}
Time Complexity: O(n log n)
Auxiliary Space: O(n), for storing the tail array.
Similar Reads
C++ Programming Language C++ is a computer programming language developed by Bjarne Stroustrup as an extension of the C language. It is known for is fast speed, low level memory management and is often taught as first programming language. It provides:Hands-on application of different programming concepts.Similar syntax to
5 min read
Non-linear Components In electrical circuits, Non-linear Components are electronic devices that need an external power source to operate actively. Non-Linear Components are those that are changed with respect to the voltage and current. Elements that do not follow ohm's law are called Non-linear Components. Non-linear Co
11 min read
Object Oriented Programming in C++ Object Oriented Programming - As the name suggests uses objects in programming. Object-oriented programming aims to implement real-world entities like inheritance, hiding, polymorphism, etc. in programming. The main aim of OOP is to bind together the data and the functions that operate on them so th
5 min read
Spring Boot Tutorial Spring Boot is a Java framework that makes it easier to create and run Java applications. It simplifies the configuration and setup process, allowing developers to focus more on writing code for their applications. This Spring Boot Tutorial is a comprehensive guide that covers both basic and advance
10 min read
Class Diagram | Unified Modeling Language (UML) A UML class diagram is a visual tool that represents the structure of a system by showing its classes, attributes, methods, and the relationships between them. It helps everyone involved in a projectâlike developers and designersâunderstand how the system is organized and how its components interact
12 min read
Steady State Response In this article, we are going to discuss the steady-state response. We will see what is steady state response in Time domain analysis. We will then discuss some of the standard test signals used in finding the response of a response. We also discuss the first-order response for different signals. We
9 min read
Backpropagation in Neural Network Back Propagation is also known as "Backward Propagation of Errors" is a method used to train neural network . Its goal is to reduce the difference between the modelâs predicted output and the actual output by adjusting the weights and biases in the network.It works iteratively to adjust weights and
9 min read
Inheritance in C++ The capability of a class to derive properties and characteristics from another class is called Inheritance. Inheritance is one of the most important features of Object-Oriented Programming in C++. In this article, we will learn about inheritance in C++, its modes and types along with the informatio
10 min read
Polymorphism in Java Polymorphism in Java is one of the core concepts in object-oriented programming (OOP) that allows objects to behave differently based on their specific class type. The word polymorphism means having many forms, and it comes from the Greek words poly (many) and morph (forms), this means one entity ca
7 min read
3-Phase Inverter An inverter is a fundamental electrical device designed primarily for the conversion of direct current into alternating current . This versatile device , also known as a variable frequency drive , plays a vital role in a wide range of applications , including variable frequency drives and high power
13 min read