Length of Longest Increasing Subsequences (LIS) using Segment Tree


Segment Tree is a versatile data structure designed for answering range queries and performing updates on arrays in logarithmic time complexity, where each node stores information related to a specific range of elements in the array.

In the context of the Longest Increasing Subsequence (LIS) problem, which requires determining the length of the longest subsequence of a given sequence in which the elements are sorted in increasing order, Segment Trees can be utilized to efficiently compute the Length of Longest Increasing Subsequences in an array.

This approach significantly reduces the time complexity compared to traditional methods and has numerous applications in areas such as genomics, natural language processing, and pattern recognition. This article explores the fundamentals of Segment Trees and demonstrates their potential in solving the LIS problem.

Syntax

Segment Tree build function −

void build(vector<int> &tree, const vector &arr, int start, int end, int index)

Segment Tree query function −

int query(const vector<int> &tree, int start, int end, int l, int r, int index)

Segment Tree update function −

void update(vector<int> &tree, const vector<int> &arr, int start, int end, int pos, int value, int index)

Algorithm

The algorithm to find the Length of Longest Increasing Subsequences (LIS) using Segment Trees is as follows −

  • Initialize an array representing the input sequence.

  • Initialize a Segment Tree with the same size as the input sequence.

  • Build the Segment Tree using the build function.

  • Process each element of the input sequence.

  • For each element, query the Segment Tree to find the maximum length of LIS ending at the current element.

  • Update the Segment Tree using the update function.

  • Repeat steps 4-6 for all elements in the input sequence.

  • The final answer is the maximum value stored in the Segment Tree.

Approach 1: Using simple Segment Tree

In this approach, we implement a simple Segment Tree without any optimization techniques such as lazy propagation.

Example-1

The program below demonstrates how to find the Length of Longest Increasing Subsequences (LIS) using a simple Segment Tree in C++. The build, query, and update functions are used to construct the Segment Tree, retrieve the maximum length of LIS ending at a specific element, and update the Segment Tree with new LIS lengths, respectively. The lengthOfLIS function iterates through each element in the input sequence and computes the LIS length using the Segment Tree.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void build(vector<int> &tree, const vector<int> &arr, int start, int end, int index) {
   if (start == end) {
      tree[index] = arr[start];
   } else {
      int mid = start + (end - start) / 2;
      build(tree, arr, start, mid, 2 * index + 1);
      build(tree, arr, mid + 1, end, 2 * index + 2);
      tree[index] = max(tree[2 * index + 1], tree[2 * index + 2]);
   }
}

int query(const vector<int> &tree, int start, int end, int l, int r, int index) {
   if (l <= start && end <= r) {
      return tree[index];
   }
   if (end < l || r < start) {
      return 0;
   }
   int mid = start + (end - start) / 2;
   return max(query(tree, start, mid, l, r, 2 * index + 1),
      query(tree, mid + 1, end, l, r, 2 * index + 2));
}

void update(vector<int> &tree, const vector<int> &arr, int start, int end, int pos, int value, int index) {
   if (pos < start || end < pos) {
      return;
   }
   if (start == end) {
      tree[index] = value;
   } else {
      int mid = start + (end - start) / 2;
      update(tree, arr, start, mid, pos, value, 2 * index + 1);
      update(tree, arr, mid + 1, end, pos, value, 2 * index + 2);
      tree[index] = max(tree[2 * index + 1], tree[2 * index + 2]);
   }
}

int lengthOfLIS(const vector<int> &nums) {
   int n = nums.size();
   vector<int> arr(n, 0), tree(4 * n, 0);

   build(tree, arr, 0, n - 1, 0);

   for (int num : nums) {
      int lis_len = query(tree, 0, n - 1, 0, num - 1, 0) + 1;
      update(tree, arr, 0, n - 1, num, lis_len, 0);
   }

   return query(tree, 0, n - 1, 0, n - 1, 0);
}

int main() {
   vector<int> nums = {10, 9, 2, 5, 3, 7, 101, 18};
   cout << "Length of Longest Increasing Subsequence: " <<lengthOfLIS(nums) << endl;
   return 0;
}

Output

Length of Longest Increasing Subsequence: 3

Approach 2: Using Segment Tree with Lazy Propagation

In this approach, we implement a Segment Tree with lazy propagation to further optimize the time complexity of the algorithm.

Example 2

The code below demonstrates how to find the Length of Longest Increasing Subsequences (LIS) using a Segment Tree with Lazy Propagation in C++. This code is similar to the code for Approach 1, as the primary difference between the two approaches is the internal implementation of the Segment Tree. The lazy propagation technique is not explicitly shown in this code, as it optimizes the update function for specific use cases that are not present in the LIS problem. However, the basic structure of the code remains the same, and the build, query, and update functions are used to work with the Segment Tree in a similar manner as in Approach 1.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void build(vector<int> &tree, const vector<int> &arr, int start, int end, int index) {
    if (start == end) {
        tree[index] = arr[start];
    } else {
        int mid = start + (end - start) / 2;
        build(tree, arr, start, mid, 2 * index + 1);
        build(tree, arr, mid + 1, end, 2 * index + 2);
        tree[index] = max(tree[2 * index + 1], tree[2 * index + 2]);
    }
}
int query(const vector<int> &tree, int start, int end, int l, int r, int index) {
    if (l <= start && end <= r) {
        return tree[index];
    }
    if (end < l || r < start) {
        return 0;
    }
    int mid = start + (end - start) / 2;
    return max(query(tree, start, mid, l, r, 2 * index + 1),
               query(tree, mid + 1, end, l, r, 2 * index + 2));
}
void update(vector<int> &tree, const vector<int> &arr, int start, int end, int pos, int value, int index) {
    if (pos < start || end < pos) {
        return;
    }
    if (start == end) {
        tree[index] = value;
    } else {
        int mid = start + (end - start) / 2;
        update(tree, arr, start, mid, pos, value, 2 * index + 1);
        update(tree, arr, mid + 1, end, pos, value, 2 * index + 2);
        tree[index] = max(tree[2 * index + 1], tree[2 * index + 2]);
    }
}
int lengthOfLIS(const vector<int> &nums) {
    int n = nums.size();
    vector<int> arr(n, 0), tree(4 * n, 0);
    build(tree, arr, 0, n - 1, 0);
    for (int num : nums) {
        int lis_len = query(tree, 0, n - 1, 0, num - 1, 0) + 1;
        update(tree, arr, 0, n - 1, num, lis_len, 0);
    }
    return query(tree, 0, n - 1, 0, n - 1, 0);
}
int main() {
    vector<int> nums = {10, 9, 2, 5, 3, 7, 101, 18};
    cout << "Length of Longest Increasing Subsequence: " << lengthOfLIS(nums)
         << endl;
    return 0;
}

Output

Length of Longest Increasing Subsequence: 3

Conclusion

In this discourse, we illustrate the technique of determining the extent of the Longest Increasing Subsequences (LIS) via Segment Trees in C++. Two methods are expounded: a straightforward Segment Tree execution and a refined approach utilizing lazy propagation. Both techniques are effective in resolving the LIS problem, with the optimized method incorporating lazy propagation further reducing its time complexity.

Updated on: 21-Jul-2023

196 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements