Given an array arr[] containing n positive integers, find the length of the longest bitonic subsequence. A subsequence of numbers is called bitonic if it is first strictly increasing, then strictly decreasing.
Note: Only strictly increasing (no decreasing part) or a strictly decreasing sequence should not be considered as a bitonic sequence.
Examples:
Input: arr[] = [1, 2, 5, 3, 2]
Output: 5
Explanation: The sequence [1, 2, 5] is increasing and the sequence [3, 2] is decreasing so merging both we will get length 5.Input: arr[] = [1, 11, 2, 10, 4, 5, 2, 1]
Output: 6
Explanation: The bitonic sequence [1, 2, 10, 4, 2, 1] has length 6.
Table of Content
[Naive Approach] Generate All Subsequences - O(2^n x n) Time and O(n) Space
The idea is that Generate every possible subsequence and check whether it is bitonic. For every subsequence:
- Find the point where the sequence changes from increasing to decreasing.
- Ensure the increasing part is strictly increasing.
- Ensure the decreasing part is strictly decreasing.
- Ensure both parts are non-empty.
This approach is mainly useful for understanding the problem and for very small arrays.
#include <iostream>
#include <vector>
using namespace std;
bool isBitonic(const vector<int>& seq) {
int n = seq.size();
if (n < 3)
return false;
int i = 1;
// Strictly increasing part.
while (i < n && seq[i] > seq[i - 1]) {
i++;
}
// There must be a decreasing part.
if (i == 1 || i == n)
return false;
// Strictly decreasing part.
while (i < n && seq[i] < seq[i - 1]) {
i++;
}
return i == n;
}
int longestBitonicSequence(vector<int>& arr) {
int n = arr.size();
int maxLen = 0;
for (int mask = 0; mask < (1 << n); mask++) {
vector<int> seq;
for (int i = 0; i < n; i++) {
if (mask & (1 << i)) {
seq.push_back(arr[i]);
}
}
if (isBitonic(seq)) {
maxLen = max(maxLen, (int)seq.size());
}
}
return maxLen;
}
int main() {
vector<int> arr1 = {1, 2, 5, 3, 2};
cout << longestBitonicSequence(arr1) << endl;
vector<int> arr2 = {1, 11, 2, 10, 4, 5, 2, 1};
cout << longestBitonicSequence(arr2) << endl;
return 0;
}
class GFG {
static boolean isBitonic(int[] seq, int size) {
if (size < 3)
return false;
int i = 1;
// Strictly increasing part.
while (i < size && seq[i] > seq[i - 1]) {
i++;
}
// There must be a decreasing part.
if (i == 1 || i == size)
return false;
// Strictly decreasing part.
while (i < size && seq[i] < seq[i - 1]) {
i++;
}
return i == size;
}
static int longestBitonicSequence(int[] arr) {
int n = arr.length;
int maxLen = 0;
for (int mask = 0; mask < (1 << n); mask++) {
int[] seq = new int[n];
int size = 0;
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) {
seq[size++] = arr[i];
}
}
if (isBitonic(seq, size)) {
maxLen = Math.max(maxLen, size);
}
}
return maxLen;
}
public static void main(String[] args) {
int[] arr1 = {1, 2, 5, 3, 2};
System.out.println(longestBitonicSequence(arr1));
int[] arr2 = {1, 11, 2, 10, 4, 5, 2, 1};
System.out.println(longestBitonicSequence(arr2));
}
}
def is_bitonic(seq: list[int]) -> bool:
n = len(seq)
if n < 3:
return False
i = 1
# Strictly increasing part.
while i < n and seq[i] > seq[i - 1]:
i += 1
# There must be a decreasing part.
if i == 1 or i == n:
return False
# Strictly decreasing part.
while i < n and seq[i] < seq[i - 1]:
i += 1
return i == n
def longestBitonicSequence(arr: list[int]) -> int:
n = len(arr)
max_len = 0
for mask in range(1 << n):
seq = []
for i in range(n):
if mask & (1 << i):
seq.append(arr[i])
if is_bitonic(seq):
max_len = max(max_len, len(seq))
return max_len
if __name__ == "__main__":
arr1 = [1, 2, 5, 3, 2]
print(longestBitonicSequence(arr1))
arr2 = [1, 11, 2, 10, 4, 5, 2, 1]
print(longestBitonicSequence(arr2))
using System;
using System.Collections.Generic;
class GFG
{
static bool IsBitonic(List<int> seq)
{
int n = seq.Count;
if (n < 3)
return false;
int i = 1;
// Strictly increasing part.
while (i < n && seq[i] > seq[i - 1])
{
i++;
}
// There must be a decreasing part.
if (i == 1 || i == n)
return false;
// Strictly decreasing part.
while (i < n && seq[i] < seq[i - 1])
{
i++;
}
return i == n;
}
static int longestBitonicSequence(int[] arr)
{
int n = arr.Length;
int maxLen = 0;
for (int mask = 0; mask < (1 << n); mask++)
{
List<int> seq = new List<int>();
for (int i = 0; i < n; i++)
{
if ((mask & (1 << i)) != 0)
{
seq.Add(arr[i]);
}
}
if (IsBitonic(seq))
{
maxLen = Math.Max(maxLen, seq.Count);
}
}
return maxLen;
}
public static void Main()
{
int[] arr1 = { 1, 2, 5, 3, 2 };
Console.WriteLine(longestBitonicSequence(arr1));
int[] arr2 = { 1, 11, 2, 10, 4, 5, 2, 1 };
Console.WriteLine(longestBitonicSequence(arr2));
}
}
'use strict';
function isBitonic(seq) {
const n = seq.length;
if (n < 3)
return false;
let i = 1;
// Strictly increasing part.
while (i < n && seq[i] > seq[i - 1]) {
i++;
}
// There must be a decreasing part.
if (i === 1 || i === n)
return false;
// Strictly decreasing part.
while (i < n && seq[i] < seq[i - 1]) {
i++;
}
return i === n;
}
/*
* @param {number[]} arr
* @return {number}
*/
function longestBitonicSequence(arr) {
const n = arr.length;
let maxLen = 0;
for (let mask = 0; mask < (1 << n); mask++) {
const seq = [];
for (let i = 0; i < n; i++) {
if (mask & (1 << i)) {
seq.push(arr[i]);
}
}
if (isBitonic(seq)) {
maxLen = Math.max(maxLen, seq.length);
}
}
return maxLen;
}
// Driver Code
const arr1 = [1, 2, 5, 3, 2];
console.log(longestBitonicSequence(arr1));
const arr2 = [1, 11, 2, 10, 4, 5, 2, 1];
console.log(longestBitonicSequence(arr2));
Output
5 6
[Better Approach] Dynamic Programming - O(n ^ 2) Time and O(n) Space
For every index i, calculate:
- lis[i] = length of the longest strictly increasing subsequence ending at i.
- lds[i] = length of the longest strictly decreasing subsequence starting at i.
If i is the peak, then: lis[i] + lds[i] - 1 gives the length of the bitonic subsequence having arr[i] as its peak.
We only consider indices where: lis[i] > 1 && lds[i] > 1, because both the increasing and decreasing parts must exist.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int longestBitonicSequence(vector<int>& arr) {
int n = arr.size();
if (n < 3)
return 0;
vector<int> lis(n, 1);
vector<int> lds(n, 1);
// Compute LIS ending at every index.
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (arr[j] < arr[i]) {
lis[i] = max(lis[i], lis[j] + 1);
}
}
}
// Compute LDS starting at every index.
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[i]) {
lds[i] = max(lds[i], lds[j] + 1);
}
}
}
int maxLen = 0;
for (int i = 0; i < n; i++) {
// Both increasing and decreasing parts must exist.
if (lis[i] > 1 && lds[i] > 1) {
maxLen = max(maxLen, lis[i] + lds[i] - 1);
}
}
return maxLen;
}
int main() {
vector<int> arr1 = {1, 2, 5, 3, 2};
cout << longestBitonicSequence(arr1) << endl;
vector<int> arr2 = {1, 11, 2, 10, 4, 5, 2, 1};
cout << longestBitonicSequence(arr2) << endl;
return 0;
}
class GFG {
static int longestBitonicSequence(int[] arr) {
int n = arr.length;
if (n < 3)
return 0;
int[] lis = new int[n];
int[] lds = new int[n];
for (int i = 0; i < n; i++) {
lis[i] = 1;
lds[i] = 1;
}
// Compute LIS ending at every index.
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (arr[j] < arr[i]) {
lis[i] = Math.max(lis[i], lis[j] + 1);
}
}
}
// Compute LDS starting at every index.
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[i]) {
lds[i] = Math.max(lds[i], lds[j] + 1);
}
}
}
int maxLen = 0;
for (int i = 0; i < n; i++) {
// Both increasing and decreasing parts must exist.
if (lis[i] > 1 && lds[i] > 1) {
maxLen = Math.max(maxLen, lis[i] + lds[i] - 1);
}
}
return maxLen;
}
public static void main(String[] args) {
int[] arr1 = {1, 2, 5, 3, 2};
System.out.println(longestBitonicSequence(arr1));
int[] arr2 = {1, 11, 2, 10, 4, 5, 2, 1};
System.out.println(longestBitonicSequence(arr2));
}
}
def longestBitonicSequence(arr: list[int]) -> int:
n = len(arr)
if n < 3:
return 0
lis = [1] * n
lds = [1] * n
# Compute LIS ending at every index.
for i in range(n):
for j in range(i):
if arr[j] < arr[i]:
lis[i] = max(lis[i], lis[j] + 1)
# Compute LDS starting at every index.
for i in range(n - 1, -1, -1):
for j in range(i + 1, n):
if arr[j] < arr[i]:
lds[i] = max(lds[i], lds[j] + 1)
max_len = 0
for i in range(n):
# Both increasing and decreasing parts must exist.
if lis[i] > 1 and lds[i] > 1:
max_len = max(max_len, lis[i] + lds[i] - 1)
return max_len
if __name__ == "__main__":
arr1 = [1, 2, 5, 3, 2]
print(longestBitonicSequence(arr1))
arr2 = [1, 11, 2, 10, 4, 5, 2, 1]
print(longestBitonicSequence(arr2))
using System;
class GFG
{
static int longestBitonicSequence(int[] arr)
{
int n = arr.Length;
if (n < 3)
return 0;
int[] lis = new int[n];
int[] lds = new int[n];
for (int i = 0; i < n; i++)
{
lis[i] = 1;
lds[i] = 1;
}
// Compute LIS ending at every index.
for (int i = 0; i < n; i++)
{
for (int j = 0; j < i; j++)
{
if (arr[j] < arr[i])
{
lis[i] = Math.Max(lis[i], lis[j] + 1);
}
}
}
// Compute LDS starting at every index.
for (int i = n - 1; i >= 0; i--)
{
for (int j = i + 1; j < n; j++)
{
if (arr[j] < arr[i])
{
lds[i] = Math.Max(lds[i], lds[j] + 1);
}
}
}
int maxLen = 0;
for (int i = 0; i < n; i++)
{
// Both increasing and decreasing parts must exist.
if (lis[i] > 1 && lds[i] > 1)
{
maxLen = Math.Max(maxLen, lis[i] + lds[i] - 1);
}
}
return maxLen;
}
public static void Main()
{
int[] arr1 = { 1, 2, 5, 3, 2 };
Console.WriteLine(longestBitonicSequence(arr1));
int[] arr2 = { 1, 11, 2, 10, 4, 5, 2, 1 };
Console.WriteLine(longestBitonicSequence(arr2));
}
}
'use strict';
/*
* @param {number[]} arr
* @return {number}
*/
function longestBitonicSequence(arr) {
const n = arr.length;
if (n < 3)
return 0;
const lis = new Array(n).fill(1);
const lds = new Array(n).fill(1);
// Compute LIS ending at every index.
for (let i = 0; i < n; i++) {
for (let j = 0; j < i; j++) {
if (arr[j] < arr[i]) {
lis[i] = Math.max(lis[i], lis[j] + 1);
}
}
}
// Compute LDS starting at every index.
for (let i = n - 1; i >= 0; i--) {
for (let j = i + 1; j < n; j++) {
if (arr[j] < arr[i]) {
lds[i] = Math.max(lds[i], lds[j] + 1);
}
}
}
let maxLen = 0;
for (let i = 0; i < n; i++) {
// Both increasing and decreasing parts must exist.
if (lis[i] > 1 && lds[i] > 1) {
maxLen = Math.max(maxLen, lis[i] + lds[i] - 1);
}
}
return maxLen;
}
// Driver Code
const arr1 = [1, 2, 5, 3, 2];
console.log(longestBitonicSequence(arr1));
const arr2 = [1, 11, 2, 10, 4, 5, 2, 1];
console.log(longestBitonicSequence(arr2));
Output
5 6
[Expected Approach] LIS/LDS Using Binary Search - O(n log n) Time and O(n) Space
Instead of calculating LIS and LDS using O(n²) DP, calculate them in O(n log n) using the standard tails + binary search technique. For every index:
- lis[i] = LIS ending at i.
- Reverse the array and calculate LIS on it.
- The resulting values give lds[i], the longest strictly decreasing subsequence starting at i.
Then: bitonic length = lis[i] + lds[i] - 1
We only consider indices where: lis[i] > 1 && lds[i] > 1
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int longestBitonicSequence(vector<int> &arr) {
int n = arr.size();
if (n < 3)
return 0;
// Compute LIS ending at each index in O(n log n).
vector<int> lis(n, 1);
vector<int> tails;
for (int i = 0; i < n; i++) {
auto it = lower_bound(tails.begin(), tails.end(), arr[i]);
int idx = it - tails.begin();
if (it == tails.end()) {
tails.push_back(arr[i]);
} else {
*it = arr[i];
}
lis[i] = idx + 1;
}
// Compute LDS starting at each index using
// LIS on the reversed array.
vector<int> rev_arr = arr;
reverse(rev_arr.begin(), rev_arr.end());
vector<int> rev_lis(n, 1);
tails.clear();
for (int i = 0; i < n; i++) {
auto it = lower_bound(tails.begin(), tails.end(), rev_arr[i]);
int idx = it - tails.begin();
if (it == tails.end()) {
tails.push_back(rev_arr[i]);
} else {
*it = rev_arr[i];
}
rev_lis[i] = idx + 1;
}
vector<int> lds(n);
for (int i = 0; i < n; i++) {
lds[i] = rev_lis[n - 1 - i];
}
int maxLen = 0;
for (int i = 0; i < n; i++) {
// Both increasing and decreasing parts must exist.
if (lis[i] > 1 && lds[i] > 1) {
maxLen = max(maxLen, lis[i] + lds[i] - 1);
}
}
return maxLen;
}
int main() {
vector<int> arr1 = {1, 2, 5, 3, 2};
cout << longestBitonicSequence(arr1) << endl;
// Output: 5
vector<int> arr2 = {1, 11, 2, 10, 4, 5, 2, 1};
cout << longestBitonicSequence(arr2) << endl;
// Output: 6
return 0;
}
class GFG {
static int longestBitonicSequence(int[] arr) {
int n = arr.length;
if (n < 3)
return 0;
// Compute LIS ending at each index in O(n log n).
int[] lis = new int[n];
int[] tails = new int[n];
int size = 0;
for (int i = 0; i < n; i++) {
int pos = lowerBound(tails, size, arr[i]);
tails[pos] = arr[i];
if (pos == size)
size++;
lis[i] = pos + 1;
}
// Compute LDS starting at each index using
// LIS on the reversed array.
int[] revArr = new int[n];
for (int i = 0; i < n; i++) {
revArr[i] = arr[n - 1 - i];
}
int[] revLis = new int[n];
tails = new int[n];
size = 0;
for (int i = 0; i < n; i++) {
int pos = lowerBound(tails, size, revArr[i]);
tails[pos] = revArr[i];
if (pos == size)
size++;
revLis[i] = pos + 1;
}
int[] lds = new int[n];
for (int i = 0; i < n; i++) {
lds[i] = revLis[n - 1 - i];
}
int maxLen = 0;
for (int i = 0; i < n; i++) {
// Both increasing and decreasing parts must exist.
if (lis[i] > 1 && lds[i] > 1) {
maxLen = Math.max(maxLen, lis[i] + lds[i] - 1);
}
}
return maxLen;
}
static int lowerBound(int[] arr, int size, int target) {
int low = 0;
int high = size;
while (low < high) {
int mid = low + (high - low) / 2;
if (arr[mid] >= target)
high = mid;
else
low = mid + 1;
}
return low;
}
public static void main(String[] args) {
int[] arr1 = {1, 2, 5, 3, 2};
System.out.println(longestBitonicSequence(arr1));
// Output: 5
int[] arr2 = {1, 11, 2, 10, 4, 5, 2, 1};
System.out.println(longestBitonicSequence(arr2));
// Output: 6
}
}
from bisect import bisect_left
def longestBitonicSequence(arr: list[int]) -> int:
n = len(arr)
if n < 3:
return 0
# Compute LIS ending at each index in O(n log n).
lis = [1] * n
tails = []
for i in range(n):
pos = bisect_left(tails, arr[i])
if pos == len(tails):
tails.append(arr[i])
else:
tails[pos] = arr[i]
lis[i] = pos + 1
# Compute LDS starting at each index using
# LIS on the reversed array.
rev_arr = arr[::-1]
rev_lis = [1] * n
tails = []
for i in range(n):
pos = bisect_left(tails, rev_arr[i])
if pos == len(tails):
tails.append(rev_arr[i])
else:
tails[pos] = rev_arr[i]
rev_lis[i] = pos + 1
lds = [0] * n
for i in range(n):
lds[i] = rev_lis[n - 1 - i]
max_len = 0
for i in range(n):
# Both increasing and decreasing parts must exist.
if lis[i] > 1 and lds[i] > 1:
max_len = max(max_len, lis[i] + lds[i] - 1)
return max_len
if __name__ == "__main__":
arr1 = [1, 2, 5, 3, 2]
print(longestBitonicSequence(arr1))
# Output: 5
arr2 = [1, 11, 2, 10, 4, 5, 2, 1]
print(longestBitonicSequence(arr2))
# Output: 6
using System;
class GFG
{
static int longestBitonicSequence(int[] arr)
{
int n = arr.Length;
if (n < 3)
return 0;
// Compute LIS ending at each index in O(n log n).
int[] lis = new int[n];
int[] tails = new int[n];
int size = 0;
for (int i = 0; i < n; i++)
{
int pos = LowerBound(tails, size, arr[i]);
tails[pos] = arr[i];
if (pos == size)
size++;
lis[i] = pos + 1;
}
// Compute LDS starting at each index using
// LIS on the reversed array.
int[] revArr = new int[n];
for (int i = 0; i < n; i++)
{
revArr[i] = arr[n - 1 - i];
}
int[] revLis = new int[n];
tails = new int[n];
size = 0;
for (int i = 0; i < n; i++)
{
int pos = LowerBound(tails, size, revArr[i]);
tails[pos] = revArr[i];
if (pos == size)
size++;
revLis[i] = pos + 1;
}
int[] lds = new int[n];
for (int i = 0; i < n; i++)
{
lds[i] = revLis[n - 1 - i];
}
int maxLen = 0;
for (int i = 0; i < n; i++)
{
// Both increasing and decreasing parts must exist.
if (lis[i] > 1 && lds[i] > 1)
{
maxLen = Math.Max(maxLen, lis[i] + lds[i] - 1);
}
}
return maxLen;
}
static int LowerBound(int[] arr, int size, int target)
{
int low = 0;
int high = size;
while (low < high)
{
int mid = low + (high - low) / 2;
if (arr[mid] >= target)
high = mid;
else
low = mid + 1;
}
return low;
}
public static void Main()
{
int[] arr1 = { 1, 2, 5, 3, 2 };
Console.WriteLine(longestBitonicSequence(arr1));
// Output: 5
int[] arr2 = { 1, 11, 2, 10, 4, 5, 2, 1 };
Console.WriteLine(longestBitonicSequence(arr2));
// Output: 6
}
}
'use strict';
/*
* @param {number[]} arr
* @return {number}
*/
function longestBitonicSequence(arr) {
const n = arr.length;
if (n < 3)
return 0;
// Compute LIS ending at each index in O(n log n).
const lis = new Array(n).fill(1);
let tails = [];
for (let i = 0; i < n; i++) {
const pos = lowerBound(tails, arr[i]);
if (pos === tails.length) {
tails.push(arr[i]);
} else {
tails[pos] = arr[i];
}
lis[i] = pos + 1;
}
// Compute LDS starting at each index using
// LIS on the reversed array.
const revArr = [...arr].reverse();
const revLis = new Array(n).fill(1);
tails = [];
for (let i = 0; i < n; i++) {
const pos = lowerBound(tails, revArr[i]);
if (pos === tails.length) {
tails.push(revArr[i]);
} else {
tails[pos] = revArr[i];
}
revLis[i] = pos + 1;
}
const lds = new Array(n);
for (let i = 0; i < n; i++) {
lds[i] = revLis[n - 1 - i];
}
let maxLen = 0;
for (let i = 0; i < n; i++) {
// Both increasing and decreasing parts must exist.
if (lis[i] > 1 && lds[i] > 1) {
maxLen = Math.max(maxLen, lis[i] + lds[i] - 1);
}
}
return maxLen;
}
function lowerBound(arr, target) {
let low = 0;
let high = arr.length;
while (low < high) {
const mid = low + Math.floor((high - low) / 2);
if (arr[mid] >= target) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
// Driver Code
const arr1 = [1, 2, 5, 3, 2];
console.log(longestBitonicSequence(arr1));
// Output: 5
const arr2 = [1, 11, 2, 10, 4, 5, 2, 1];
console.log(longestBitonicSequence(arr2));
// Output: 6
Output
5 6