Dynamic Programming | Set 15 (Longest Bitonic Subsequence)
Given an array arr[0 ... n-1] containing n positive integers, a subsequence of arr[] is called Bitonic if it is first increasing, then decreasing. Write a function that takes an array as argument and returns the length of the longest bitonic subsequence.
A sequence, sorted in increasing order is considered Bitonic with the decreasing part as empty. Similarly, decreasing order sequence is considered Bitonic with the increasing part as empty.
Examples:
Input arr[] = {1, 11, 2, 10, 4, 5, 2, 1};
Output: 6 (A Longest Bitonic Subsequence of length 6 is 1, 2, 10, 4, 2, 1)
Input arr[] = {12, 11, 40, 5, 3, 1}
Output: 5 (A Longest Bitonic Subsequence of length 5 is 12, 11, 5, 3, 1)
Input arr[] = {80, 60, 30, 40, 20, 10}
Output: 5 (A Longest Bitonic Subsequence of length 5 is 80, 60, 30, 20, 10)
Source: Microsoft Interview Question
Solution
This problem is a variation of standard Longest Increasing Subsequence (LIS) problem. Let the input array be arr[] of length n. We need to construct two arrays lis[] and lds[] using Dynamic Programming solution of LIS problem. lis[i] stores the length of the Longest Increasing subsequence ending with arr[i]. lds[i] stores the length of the longest Decreasing subsequence starting from arr[i]. Finally, we need to return the max value of lis[i] + lds[i] – 1 where i is from 0 to n-1.
Following is C++ implementation of the above Dynamic Programming solution.
/* Dynamic Programming implementation of longest bitonic subsequence problem */
#include<stdio.h>
#include<stdlib.h>
/* lbs() returns the length of the Longest Bitonic Subsequence in
arr[] of size n. The function mainly creates two temporary arrays
lis[] and lds[] and returns the maximum lis[i] + lds[i] - 1.
lis[i] ==> Longest Increasing subsequence ending with arr[i]
lds[i] ==> Longest decreasing subsequence starting with arr[i]
*/
int lbs( int arr[], int n )
{
int i, j;
/* Allocate memory for LIS[] and initialize LIS values as 1 for
all indexes */
int *lis = new int[n];
for ( i = 0; i < n; i++ )
lis[i] = 1;
/* Compute LIS values from left to right */
for ( i = 1; i < n; i++ )
for ( j = 0; j < i; j++ )
if ( arr[i] > arr[j] && lis[i] < lis[j] + 1)
lis[i] = lis[j] + 1;
/* Allocate memory for lds and initialize LDS values for
all indexes */
int *lds = new int [n];
for ( i = 0; i < n; i++ )
lds[i] = 1;
/* Compute LDS values from right to left */
for ( i = n-2; i >= 0; i-- )
for ( j = n-1; j > i; j-- )
if ( arr[i] > arr[j] && lds[i] < lds[j] + 1)
lds[i] = lds[j] + 1;
/* Return the maximum value of lis[i] + lds[i] - 1*/
int max = lis[0] + lds[0] - 1;
for (i = 1; i < n; i++)
if (lis[i] + lds[i] - 1 > max)
max = lis[i] + lds[i] - 1;
return max;
}
/* Driver program to test above function */
int main()
{
int arr[] = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15};
int n = sizeof(arr)/sizeof(arr[0]);
printf("Length of LBS is %d\n", lbs( arr, n ) );
getchar();
return 0;
}
Output:
Length of LBS is 7
Time Complexity: O(n^2)
Auxiliary Space: O(n)
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above
why din't you use nlogn LIS to make it o(nlogn)???
In this Method you don't need to traverse the sequence two times
We are storing two things in dp... first is the length of the bitonic sequence ending at index i and also what is the current phase of bitonic sequence i.e. whether it's still increasing or it is now in decreasing phase
/* Paste your code here (You may delete these lines if not writing code) */ #include<stdio.h> #include<iostream> #include<vector> #define F first #define S second using namespace std; vector<pair<int,int> >dp; int arr[100]; int main() { int i,j,n; int max=0; cin>>n; dp.resize(n+1); for(i=0;i<n;i++) { cin>>arr[i]; dp[i]=make_pair(1,1); //initialisation, first element tells the size of sequence while second element tells whether seq is increasing or decreasing } max=0; for(i=1;i<n;i++) { for(j=0;j<i;j++) { if((dp[j].S==0 && arr[i]<=arr[j]) && dp[i].F<dp[j].F+1 ) { dp[i].F=dp[j].F+1; dp[i].S=0; } else if(dp[j].S==1 && dp[i].F<dp[j].F+1) { if(arr[i]>=arr[j]) dp[i].S=1; else dp[i].S=0; dp[i].F=dp[j].F+1; } } if(max<dp[i].F) max=dp[i].F; } cout<<"Ans:\t"<<max<<endl; return 0; }In this Method you don't need to traverse the sequence two times
We are storing two things in dp... first is the length of the bitonic sequence ending at index i and also what is the current phase of bitonic sequence i.e. whether it's still increasing or it is now in decreasing phase
#include
#include
#include
#define F first
#define S second
using namespace std;
vector<pair >dp;
int arr[100];
int main()
{
int i,j,n;
int max=0;
cin>>n;
dp.resize(n+1);
for(i=0;i>arr[i];
dp[i]=make_pair(1,1); //initialisation, first element tells the size of sequence while second element tells whether seq is increasing or decreasing
}
max=0;
for(i=1;i<n;i++)
{
for(j=0;j<i;j++)
{
if((dp[j].S==0 && arr[i]<=arr[j]) && dp[i].F<dp[j].F+1 )
{
dp[i].F=dp[j].F+1;
dp[i].S=0;
}
else if(dp[j].S==1 && dp[i].F=arr[j])
dp[i].S=1;
else
dp[i].S=0;
dp[i].F=dp[j].F+1;
}
}
if(max<dp[i].F)
max=dp[i].F;
}
cout<<"Ans:\t"<<max<<endl;
return 0;
}
This should be solved in O(nlogn) time now it is question which is combination of longest increasing subsequence and longest decreasing subsequence.
Just a minor edit - you guys haven't freed lis and lds before returning max.
No need to allocate separate memory for both lis and lds. We could manage in one. Let me know in case of any issues.
int longestBitonic(int a[], int size) { int dp[size], i, j, max = 1; dp[0] = 1; for(i=1;i<size;i++) { dp[i] = 1; for(j=i-1;j>=0;j--) { if((dp[i]<dp[j]+1) && a[i]>a[j]) { dp[i] = dp[j]+1; } } if(dp[i]>max) { max = dp[i]; } } dp[size-1] = 1; for(i=size-2;i>=0;i--) { int prev = dp[i]; dp[i] = 1; for(j=i+1;j<size;j++) { if((dp[i]<dp[j]+1) && a[i]>a[j]) { dp[i] = dp[j]+1; } } if(prev+dp[i]-1>max) { max = prev+dp[i]-1; } } return max; }Brilliant!
On a closer looks, I'm afraid I don't think it works. You'll end up adding LIS with the LBS of the next element, which in the following case will fail.
1, 5, 2, 4, 3
You'll end up finding a mountain range when all you need to find is a hill.
Expected answer: 3
Your solution: 6 (Erroneous stmt: LBS[1] = LBS[3] + LIS[1] - 1)
I dont think below statements are requirement while computing the LIS and LDS in the if statement because in any case for any index if we get the number which in increasing (from left) or decreasing (fron right) and in the lis or lds the below statements is always true so need to check it again. Am I right?
lis[i] < lis[j] + 1
lds[i] < lds[j] + 1
public class longestBiotonicSubsequence { public static void main(String str[]){ int array[] = {1, 11, 2, 10, 4, 5, 2, 1}; BiotonicObj b =calculateLongest(array,0,array.length-1); System.out.println(b.length); } public static BiotonicObj calculateLongest(int[] array,int start,int end){ if(start<end){ int i=start; int count=0; while(array[i]<array[i+1]){ i++; count++; } int negativecount = 0; while(array[i]>array[i+1] && negativecount<count ){ i++; negativecount++; } if(negativecount==0 && count==0) { return calculateLongest(array, start+1,end); }else{ int startingIndex=start+(count-negativecount); int endint = i; BiotonicObj obj= calculateLongest(array, i,end); if(obj.length > endint-startingIndex+1) return obj; else{ BiotonicObj ret = new BiotonicObj(); ret.start=startingIndex; ret.end=endint; ret.length = endint-startingIndex+1; return ret; } } }else{ BiotonicObj ret = new BiotonicObj(); ret.start=start; ret.end=end; ret.length =1; return ret; } } } class BiotonicObj{ public int start; public int end; public int length; }This can also be solved simply by using longest increasing sub sequence and longest decreasing sub sequence ..
Store longest_increasing[i] stores values of longest increasing sub sequence till i.
Store longest_decreasing[i] stores values of longest decreasing sub sequence till i.
Longest_bitonic[i]=longest_decreasing[i] + longest_decreasing[i]
then find max in Longest_bitonic
Longest_bitonic[i]=longest_decreasing[i] + longest_decreasing[i] -1
Looks like the owner has used the same method
The examples are wrong. In 2nd and 3rd example, Outputs shown are strictly decreasing sequences.
Please take a closer look at the problem statement. Especially the following part:
"A sequence, sorted in increasing order is considered Bitonic with the decreasing part as empty. Similarly, decreasing order sequence is considered Bitonic with the increasing part as empty."
LIS has an O(n*log(n)) solution. So I think this one should be solveble in O(n*log(n)) time and O(n) space, as demonstrated in the following code.
template<typename citer> void LIS(citer begin, citer end, function<void (typename citer::value_type)> fn) { typedef vector<typename citer::value_type> lut_t; lut_t lut; for (citer i = begin; i != end; ++i) { lut_t::iterator p = upper_bound(lut.begin(), lut.end(), *i); if (p == lut.end()) { fn(lut.size()); lut.push_back(*i); } else { fn(p - lut.begin()); *p = min(*p, *i); } } } template<typename T> int LongestBitonicSequence(const vector<T>& A) { stack<T> tmp; LIS(A.begin(), A.end(), [&tmp](T n){ tmp.push(n); }); T result = T(); LIS(A.rbegin(), A.rend(), [&tmp, &result](T n){ result = max(result, n+tmp.top()); tmp.pop(); }); return result+1; } void testLongestBitonicSequence() { int const a[] = {1, 11, 2, 10, 4, 5, 2, 1}; assert(6 == LongestBitonicSequence(vector<int>(a, a+ARRAY_LENGTH(a)))); int const b[] = {12, 11, 40, 5, 3, 1}; assert(5 == LongestBitonicSequence(vector<int>(b, b+ARRAY_LENGTH(b)))); int const c[] = {80, 60, 30, 40, 20, 10}; assert(5 == LongestBitonicSequence(vector<int>(c, c+ARRAY_LENGTH(c)))); }Does the question says that the sequence first need to increase and then decrease or vice versa but the algo presented here does not seems to do so...... correct me if i am wrong.
Please take a closer look at the problem statement and solution. It says that the sequence should be first increasing, then decreasing. Not vice versa.
simply rocking
Second loop should start from (n-2), as it saves one extra iteration.
Thanks for suggesting the optimization. We have changed it to start from n-2.
Super!
Awesome