Given a sequence as as array of positive integers. Find the length of longest bitonic subsequence.
A bitonic subsequence is a subsequence that is first increasing up to a peak value and then decreasing from the peak value. For example, A=[1, 11, 2, 10, 4, 5, 2, 1] the longest bitonic sequence is 1, 2, 4, 5, 2, 1 of length 6. For A=[0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15] longest bitonic sequence is 0, 8, 12, 14, 13, 11, 7 of length 7.
Note that a bitoic sequence starting from a value reaches a peak value in a strict increasing order of values. Then it start decreasing monotonically. So, we can easily perceive that a bitonic sequence consists of a increasing subsequence and a decreasing subsequence. So, a longest bitonic subsequence would be subsequence that consists of a longest increasing subsequence (LIS) ending at peak and a longest decreasing subsequence (LDS) starting at peak. For example longest bitonic subsequence of A=[0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15] can be represented as follows.
LIS peak LDS | | | | v | | 14 | v 12 13 v 8 11 0 7
So, the longest bitonic subsequence with peak at a position i would consists of longest increasing subsequence that ends at i and a longest decreasing subsequence starting at i. We need to construct two arrays LIS[] and LDS[] such that for each position i –
LIS[i] : length of the Longest Increasing subsequence ending at arr[i]. LDS[i]: length of the longest Decreasing subsequence starting from arr[i]. LIS[i]+LDS[i]-1 : the length Longest Bitonic Subsequence with peak at i.
We need to find the position i with maximum value of LIS[i]+LDS[i]-1. We can easily solve this problem by solving the Longest Increasing Subsequence (LIS) problem.
Longest Increasing Subsequence (LIS) – Dynamic Programming
First of all, lets focus how to compute longest increasing subsequence. Let’s consider LIS(i) be the length of longest increasing subsequence of the sequence at position i i.e. A[0..i] where A is the input sequence.
For example, A=[0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15] –
LIS(0) = 1 [0] ---> a singe number is an increasing subsequence itself LIS(1) = 2 [0, 8] LIS(2) = 2 [0,4] LIS(3) = 3 [0, 8, 12] or [0, 4, 12] LIS(4) = 2 [0, 2] LIS(5) = 3 [0, 8, 10] or [0, 4, 10] or [0, 2, 10] LIS(6) = 3 [0, 4, 6] or [0, 2, 6] LIS(7) = 4 [0, 8, 12, 14] or [0, 4, 12, 14] or [0, 8, 10, 14] or [0, 4, 10, 14] or [0, 4, 6, 14] or [0, 2, 6, 14] .... ....
Note that, LIS(7) is constructed by appending A[7] to the solutions of LIS(3), LIS(5), and LIS(6). If we look closely to other solutions then we will se that LIS(i) is constructed by appending A[i] to the maximum length solutions LIS(j) for all j < i and A[j] < A[i]. So, we can construct a recurrence relation
LIS(i) = max{1+LIS(j)} for all j < i and A[j] < A[i]
. The recursion tree for the above recurrence for i = 4 is as follows –
lis(4) / | \ lis(3) lis(2) lis(1) / \ / lis(2) lis(1) lis(1) / lis(1)
So, the LIS problem has an optimal substructure LIS(i) = max{1+LIS(j)} for all j < i
and the subproblems are repeating. So, we can use a Dynamic Programming (DP) table to cache the result and reuse computations.
Longest Decreasing Subsequence (LDS) can be computed similar to LIS with the recurrence relation LDS(i) = max{1+LDS(j)} for all j < i and A[j] > A[i]
. Using these two relations we can now easily compute Longest Bitonic Sequence with peak at i by LIS(i)+LDS(i)-1. We need to find a peak position i such that the value of LIS(i)+LDS(i)-1 is maximized. Following is a simple implementation of the idea which runs in O(n^2) time and O(n) auxiliary space.
public static int longestBiotonicSequence(int[] a){ int[] lis = new int[a.length]; int[] lds = new int[a.length]; //base cases - single number is a lis and lds Arrays.fill(lis, 1); Arrays.fill(lds, 1); int maxBiotonicLen = Integer.MIN_VALUE; //longest increasing subsequence //lis(i) = max{1+lis(j)}, for all j < i and a[j] < a[i] for(int i = 1; i < a.length; i++){ for(int j = 0; j < i; j++){ if(a[i] > a[j] && lis[j] + 1 > lis[i]){ lis[i] = lis[j]+1; } } } //longest decreasing subsequence //lds(i) = max{1+lds(j)}, for all j < i and a[j] > a[i] //longest biotonic seq lbs(i) = lis(i)+lds(i)-1 maxBiotonicLen = lis[0]+lds[0]-1; for(int i = 1; i < a.length; i++){ for(int j = 0; j < i; j++){ if(a[i] < a[j] && lds[j] + 1 > lds[i]){ lds[i] = lds[j]+1; } } maxBiotonicLen = Math.max(maxBiotonicLen, lis[i]+lds[i]-1); } return maxBiotonicLen; }