Longest Bitonic Subsequence

Last Updated : 24 Sep, 2026

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.

Try It Yourself
redirect icon

[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:

  1. Find the point where the sequence changes from increasing to decreasing.
  2. Ensure the increasing part is strictly increasing.
  3. Ensure the decreasing part is strictly decreasing.
  4. Ensure both parts are non-empty.

This approach is mainly useful for understanding the problem and for very small arrays.

C++
#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;
}
Java
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));
    }
}
Python
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))
C#
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));
    }
}
JavaScript
'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.

C++
#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;
}
Java
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));
    }
}
Python
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))
C#
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));
    }
}
JavaScript
'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

C++
#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;
}
Java
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
    }
}
Python
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
C#
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
    }
}
JavaScript
'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
Comment