Range Queries for Longest Correct Bracket Subsequence

Given a bracket sequence or in other words a string S of length n, consisting of characters β€˜(β€˜ and β€˜)’. Find the length of the maximum correct bracket subsequence of sequence for a given query range

Note: A correct bracket sequence is the one that has matched bracket pairs or which contains another nested correct bracket sequence. For e.g (), (()), ()() are some correct bracket sequence

Examples:

Input: S = ())(())(())(
Output: 10
Explanation:  Longest Correct Bracket Subsequence is ()(())(())

Input: S = ())(())(())(0
Output: 0

Range Queries for Longest Correct Bracket Subsequence using segment trees:

To solve the problem follow the below idea:

Segment Trees can be used to solve this problem efficiently At each node of the segment tree, we store the following:

  • Number of correctly matched pairs of brackets
  • Number of unused open brackets
  • Number of unused closed brackets

(unused open bracket – means they can’t be matched with any closing bracket, for e.g S = )( contains an unused open and an unused closed bracket) For each interval [L, R], we can match X number of unused open brackets β€˜(β€˜ in interval [L, MID] with unused closed brackets β€˜)’ in interval [MID + 1, R] where X = minimum(number of unused β€˜(β€˜ in [L, MID], number of unused β€˜)’ in [MID + 1, R]) Hence, X is also the number of correctly matched pairs built by combination. So, for interval [L, R] 

  • Total number of correctly matched pairs becomes the sum of correctly matched pairs in left child and correctly matched pairs in right child and number of combinations of unused β€˜(β€˜ and unused β€˜)’ from left and right child respectively

a[L, R] = a[L, MID] + a[MID + 1, R] + X

  • Total number of unused open brackets becomes the sum of unused open brackets in left child and unused open brackets in right child minus X (minus – because we used X unused β€˜(β€˜ from left child to match with unused β€˜) from right child)

a[L, R] = b[L, MID] + b[MID + 1, R] – X

  • Similarly, for unused closed brackets, following relation holds

a[L, R] = c[L, MID] + c[MID + 1, R] – X

where a, b and c are the representations described above for each node to be stored in

Below is the implementation of the above approach: 

C++




/* CPP Program to find the longest correct
bracket subsequence in a given range */
#include <bits/stdc++.h>
using namespace std;
 
/* Declaring Structure for storing
three values in each segment tree node */
struct Node {
    int pairs;
    int open; // unused
    int closed; // unused
 
    Node() { pairs = open = closed = 0; }
};
 
// A utility function to get the middle index from corner
// indexes.
int getMid(int s, int e) { return s + (e - s) / 2; }
 
// Returns Parent Node after merging its left and right
// child
Node merge(Node leftChild, Node rightChild)
{
    Node parentNode;
    int minMatched = min(leftChild.open, rightChild.closed);
    parentNode.pairs
        = leftChild.pairs + rightChild.pairs + minMatched;
    parentNode.open
        = leftChild.open + rightChild.open - minMatched;
    parentNode.closed
        = leftChild.closed + rightChild.closed - minMatched;
    return parentNode;
}
 
// A recursive function that constructs Segment Tree
// for string[ss..se]. si is index of current node in
// segment tree st
void constructSTUtil(char str[], int ss, int se, Node* st,
                     int si)
{
    // If there is one element in string, store it in
    // current node of segment tree and return
    if (ss == se) {
 
        // since it contains one element, pairs
        // will be zero
        st[si].pairs = 0;
 
        // check whether that one element is opening
        // bracket or not
        st[si].open = (str[ss] == '(' ? 1 : 0);
 
        // check whether that one element is closing
        // bracket or not
        st[si].closed = (str[ss] == ')' ? 1 : 0);
 
        return;
    }
 
    // If there are more than one elements, then recur
    // for left and right subtrees and store the relation
    // of values in this node
    int mid = getMid(ss, se);
    constructSTUtil(str, ss, mid, st, si * 2 + 1);
    constructSTUtil(str, mid + 1, se, st, si * 2 + 2);
 
    // Merge left and right child into the Parent Node
    st[si] = merge(st[si * 2 + 1], st[si * 2 + 2]);
}
 
/* Function to construct segment tree from given
string. This function allocates memory for segment
tree and calls constructSTUtil() to fill the
allocated memory */
Node* constructST(char str[], int n)
{
    // Allocate memory for segment tree
 
    // Height of segment tree
    int x = (int)(ceil(log2(n)));
 
    // Maximum size of segment tree
    int max_size = 2 * (int)pow(2, x) - 1;
 
    // Declaring array of structure Allocate memory
    Node* st = new Node[max_size];
 
    // Fill the allocated memory st
    constructSTUtil(str, 0, n - 1, st, 0);
 
    // Return the constructed segment tree
    return st;
}
 
/* A Recursive function to get the desired
Maximum Sum Sub-Array,
The following are parameters of the function-
 
st     --> Pointer to segment tree
si --> Index of the segment tree Node
ss & se --> Starting and ending indexes of the
            segment represented by
                current Node, i.e., tree[index]
qs & qe --> Starting and ending indexes of query range */
Node queryUtil(Node* st, int ss, int se, int qs, int qe,
               int si)
{
    // No overlap
    if (ss > qe || se < qs) {
 
        // returns a Node for out of bounds condition
        Node nullNode;
        return nullNode;
    }
 
    // Complete overlap
    if (ss >= qs && se <= qe) {
        return st[si];
    }
 
    // Partial Overlap Merge results of Left
    // and Right subtrees
    int mid = getMid(ss, se);
    Node left = queryUtil(st, ss, mid, qs, qe, si * 2 + 1);
    Node right
        = queryUtil(st, mid + 1, se, qs, qe, si * 2 + 2);
 
    // merge left and right subtree query results
    Node res = merge(left, right);
    return res;
}
 
/* Returns the maximum length correct bracket
subsequencebetween start and end
It mainly uses queryUtil(). */
int query(Node* st, int qs, int qe, int n)
{
    Node res = queryUtil(st, 0, n - 1, qs, qe, 0);
 
    // since we are storing numbers pairs
    // and have to return maximum length, hence
    // multiply no of pairs by 2
    return 2 * res.pairs;
}
 
// Driver Code
int main()
{
    char str[] = "())(())(())(";
    int n = strlen(str);
 
    // Build segment tree from given string
    Node* st = constructST(str, n);
 
      // Function call
    int startIndex = 0, endIndex = 11;
    cout << "Maximum Length Correct Bracket"
            " Subsequence between "
         << startIndex << " and " << endIndex << " = "
         << query(st, startIndex, endIndex, n) << endl;
 
    startIndex = 1, endIndex = 2;
    cout << "Maximum Length Correct Bracket"
            " Subsequence between "
         << startIndex << " and " << endIndex << " = "
         << query(st, startIndex, endIndex, n) << endl;
 
    return 0;
}


Java




// Java Program to find the longest correct bracket
// subsequence in a given range
import java.util.*;
import java.lang.*;
import java.io.*;
 
// Declaring Structure for storing three values in each segment tree node
class Node {
    int pairs;
    int open; // unused
    int closed; // unused
  
    Node() {
        pairs = 0;
        open = 0;
        closed = 0;
    }
}
 
class SegmentTree {
    Node[] st;
    int n;
  
    // A utility function to get the middle index from corner indexes.
    int getMid(int s, int e) {
        return s + (e - s) / 2;
    }
  
    // Returns Parent Node after merging its left and right child
    Node merge(Node leftChild, Node rightChild) {
        Node parentNode = new Node();
        int minMatched = Math.min(leftChild.open, rightChild.closed);
        parentNode.pairs = leftChild.pairs + rightChild.pairs + minMatched;
        parentNode.open = leftChild.open + rightChild.open - minMatched;
        parentNode.closed = leftChild.closed + rightChild.closed - minMatched;
        return parentNode;
    }
  
    // A recursive function that constructs Segment Tree for string[ss..se].
    // si is index of current node in segment tree st
    void constructSTUtil(String str, int ss, int se, int si) {
        // If there is one element in string, store it in current node of
        // segment tree and return
        if (ss == se) {
            // since it contains one element, pairs will be zero
            st[si].pairs = 0;
            // check whether that one element is opening bracket or not
            st[si].open = (str.charAt(ss) == '(') ? 1 : 0;
            // check whether that one element is closing bracket or not
            st[si].closed = (str.charAt(ss) == ')') ? 1 : 0;
            return;
        }
  
        // If there are more than one elements, then recur for left and right
        // subtrees and store the relation of values in this node
        int mid = getMid(ss, se);
        constructSTUtil(str, ss, mid, si * 2 + 1);
        constructSTUtil(str, mid + 1, se, si * 2 + 2);
  
        // Merge left and right child into the Parent Node
        st[si] = merge(st[si * 2 + 1], st[si * 2 + 2]);
    }
  
    // Function to construct segment tree from given string.
    // This function allocates memory for segment tree and calls
    // constructSTUtil() to fill the allocated memory
    void constructST(String str) {
        // Allocate memory for segment tree
        // Height of segment tree
        int x = (int) (Math.ceil(Math.log(n) / Math.log(2)));
  
        // Maximum size of segment tree
        int max_size = 2 * (int) Math.pow(2, x) - 1;
  
        st = new Node[max_size];
        for (int i = 0; i < max_size; i++)
            st[i] = new Node();
  
        // Fill the allocated memory st
        constructSTUtil(str, 0, n - 1, 0);
    }
  
    // A Recursive function to get the desired Maximum Sum Sub-Array,
    // The following are parameters of the function-
    // st     --> Pointer to segment tree
    // si --> Index of the segment tree Node
    // ss & se --> Starting and ending indexes of the segment
    // represented by current Node, i.e., tree[index]
    // qs & qe --> Starting and ending indexes of query range
    Node queryUtil(int ss, int se, int qs, int qe, int si) {
        // No overlap
        if (ss > qe || se < qs)
            // returns a Node for out of bounds condition
            return new Node();
  
        // Complete overlap
        if (ss >= qs && se <= qe)
            return st[si];
  
        // Partial Overlap Merge results of Left and Right subtrees
        int mid = getMid(ss, se);
        Node left = queryUtil(ss, mid, qs, qe, si * 2 + 1);
        Node right = queryUtil(mid + 1, se, qs, qe, si * 2 + 2);
        return merge(left, right);
    }
  
    // The function to get the maximum length of correct bracket subsequence
    // for given range. The following are parameters of the function-
    // st --> Pointer to segment tree
    // qs & qe --> Starting and ending indexes of query range
    int query(int qs, int qe) {
        Node node = queryUtil(0, n - 1, qs, qe, 0);
        return 2 * node.pairs;
    }
  
    // Driver code
    public static void main(String args[]) {
        String str = "())(())(())(";
        int n = str.length();
        SegmentTree tree = new SegmentTree();
        tree.n = n;
        tree.constructST(str);
        int qs = 0;
        int qe = n - 1;
        System.out.println("Maximum Length Correct Bracket Subsequence between " + qs + " and " + qe + " = " + tree.query(qs, qe));
  
        qs = 1;
        qe = 2;
        System.out.println("Maximum Length Correct Bracket Subsequence between " + qs + " and " + qe + " = " + tree.query(qs, qe));
    }
}


Python3




# Python Program to find the longest correct bracket
# subsequence in a given range
import math
# Declaring Structure for storing three values in each segment tree node
class Node:
    def __init__(self):
        self.pairs = 0
        self.open = 0 # unused
        self.closed = 0 # unused
 
# A utility function to get the middle index from corner indexes.
def getMid(s: int, e: int) -> int:
    return s + (e - s) // 2
 
# Returns Parent Node after merging its left and right child
def merge(leftChild: Node, rightChild: Node) -> Node:
    parentNode = Node()
    minMatched = min(leftChild.open, rightChild.closed)
    parentNode.pairs = leftChild.pairs + rightChild.pairs + minMatched
    parentNode.open = leftChild.open + rightChild.open - minMatched
    parentNode.closed = leftChild.closed + rightChild.closed - minMatched
    return parentNode
 
# A recursive function that constructs Segment Tree for string[ss..se].
# si is index of current node in segment tree st
def constructSTUtil(str: str, ss: int, se: int, st: list, si: int):
    # If there is one element in string, store it in current node of segment tree and return
    if ss == se:
        # since it contains one element, pairs will be zero
        st[si].pairs = 0
        # check whether that one element is opening bracket or not
        st[si].open = 1 if str[ss] == '(' else 0
        # check whether that one element is closing bracket or not
        st[si].closed = 1 if str[ss] == ')' else 0
        return
    # If there are more than one elements, then recur for left and right subtrees
    # and store the relation of values in this node
    mid = getMid(ss, se)
    constructSTUtil(str, ss, mid, st, si * 2 + 1)
    constructSTUtil(str, mid + 1, se, st, si * 2 + 2)
    # Merge left and right child into the Parent Node
    st[si] = merge(st[si * 2 + 1], st[si * 2 + 2])
 
# Function to construct segment tree from given string.
# This function allocates memory for segment tree and calls constructSTUtil()
# to fill the allocated memory
def constructST(str: str, n: int) -> list:
    # Allocate memory for segment tree
    # Height of segment tree
    x = int(math.ceil(math.log2(n)))
    # Maximum size of segment tree
    max_size = 2 * int(2 ** x) - 1
    # Declaring array of structure Allocate memory
    st = [Node() for _ in range(max_size)]
    # Fill the allocated memory st
    constructSTUtil(str, 0, n - 1, st, 0)
    # Return the constructed segment tree
    return st
 
# A Recursive function to get the desired Maximum Sum Sub-Array,
# The following are parameters of the function-
# st     --> Pointer to segment tree
# si --> Index of the segment tree Node
# ss & se --> Starting and ending indexes of the segment
# represented by current Node, i.e., tree[index]
# qs & qe --> Starting and ending indexes of query range
def queryUtil(st: list, ss: int, se: int, qs: int, qe: int, si: int) -> Node:
    # No overlap
    if ss > qe or se < qs:
        # returns a Node for out of bounds condition
        return Node()
    # Complete overlap
    if ss >= qs and se <= qe:
        return st[si]
    # Partial Overlap Merge results of Left and Right subtrees
    mid = getMid(ss, se)
    left = queryUtil(st, ss, mid, qs, qe, si * 2 + 1)
    right = queryUtil(st, mid + 1, se, qs, qe, si * 2 + 2)
    return merge(left, right)
 
# The function to get the maximum length of correct bracket subsequence
# for given range. The following are parameters of the function-
# st --> Pointer to segment tree
# qs & qe --> Starting and ending indexes of query range
def query(st: list, n: int, qs: int, qe: int) -> int:
    node = queryUtil(st, 0, n - 1, qs, qe, 0)
    return 2 * node.pairs
 
# Driver code
def main():
    str = "())(())(())("
    n = len(str)
    st = constructST(str, n)
    qs = 0
    qe = n - 1
    print("Maximum Length Correct Bracket Subsequence between", qs, "and", qe, "=", query(st, n, qs, qe))
 
    qs = 1
    qe = 2
    print("Maximum Length Correct Bracket Subsequence between", qs, "and", qe, "=", query(st, n, qs, qe))
 
if __name__ == '__main__':
    main()
# This code is contributed by Vikram_Shirsat


Javascript




// Javascript Program to find the longest correct bracket
// subsequence in a given range
class Node {
  constructor() {
    this.pairs = 0;
    this.open = 0; // unused
    this.closed = 0; // unused
  }
}
 
// A utility function to get the middle index from corner indexes.
function getMid(s, e) {
  return s + Math.floor((e - s) / 2);
}
 
// Returns Parent Node after merging its left and right child
function merge(leftChild, rightChild) {
  const parentNode = new Node();
  const minMatched = Math.min(leftChild.open, rightChild.closed);
  parentNode.pairs = leftChild.pairs + rightChild.pairs + minMatched;
  parentNode.open = leftChild.open + rightChild.open - minMatched;
  parentNode.closed = leftChild.closed + rightChild.closed - minMatched;
  return parentNode;
}
 
 
// A recursive function that constructs Segment Tree for string[ss..se].
// si is index of current node in segment tree st
function constructSTUtil(str, ss, se, st, si) {
    // If there is one element in string, store it in current node of segment tree and return
  if (ss === se) {
    // since it contains one element, pairs will be zero
    st[si].pairs = 0;
    // check whether that one element is opening bracket or not
    st[si].open = str[ss] === '(' ? 1 : 0;
    // check whether that one element is closing bracket or not
    st[si].closed = str[ss] === ')' ? 1 : 0;
    return;
  }
    // If there are more than one elements, then recur for left and right subtrees
    // and store the relation of values in this node
  const mid = getMid(ss, se);
  constructSTUtil(str, ss, mid, st, si * 2 + 1);
  constructSTUtil(str, mid + 1, se, st, si * 2 + 2);
  // Merge left and right child into the Parent Node
  st[si] = merge(st[si * 2 + 1], st[si * 2 + 2]);
}
 
// Function to construct segment tree from given string.
// This function allocates memory for segment tree and calls constructSTUtil()
// to fill the allocated memory
function constructST(str, n) {
   // Allocate memory for segment tree
  // Height of segment tree
  const x = Math.ceil(Math.log2(n));
  // Maximum size of segment tree
  const max_size = 2 * Math.pow(2, x) - 1;
  // Declaring array of structure Allocate memory
  const st = Array(max_size).fill().map(_ => new Node());
  // Fill the allocated memory st
  constructSTUtil(str, 0, n - 1, st, 0);
  // Return the constructed segment tree
  return st;
}
 
 
// A Recursive function to get the desired Maximum Sum Sub-Array,
// The following are parameters of the function-
// st     --> Pointer to segment tree
// si --> Index of the segment tree Node
// ss & se --> Starting and ending indexes of the segment
// represented by current Node, i.e., tree[index]
// qs & qe --> Starting and ending indexes of query range
function queryUtil(st, ss, se, qs, qe, si) {
  if (ss > qe || se < qs) {
    return new Node();
  }
  if (ss >= qs && se <= qe) {
    return st[si];
  }
  const mid = getMid(ss, se);
  const left = queryUtil(st, ss, mid, qs, qe, si * 2 + 1);
  const right = queryUtil(st, mid + 1, se, qs, qe, si * 2 + 2);
  return merge(left, right);
}
 
 
// The function to get the maximum length of correct bracket subsequence
// for given range. The following are parameters of the function-
// st --> Pointer to segment tree
// qs & qe --> Starting and ending indexes of query range
function query(st, n, qs, qe) {
  const node = queryUtil(st, 0, n - 1, qs, qe, 0);
  return 2 * node.pairs;
}
 
// Driver code
function main() {
  const str = "())(())(())(";
  const n = str.length;
  const st = constructST(str, n);
  let qs = 0;
  let qe = n - 1;
  console.log(`Maximum Length Correct Bracket Subsequence between ${qs} and ${qe} = ${query(st, n, qs, qe)}`);
 
  qs = 1;
  qe = 2;
  console.log(`Maximum Length Correct Bracket Subsequence between ${qs} and ${qe} = ${query(st, n, qs, qe)}`);
}
 
main();
 
// This code is contributed by sdeadityasharma


C#




// C# Program to find the longest correct bracket
// subsequence in a given range
 
using System;
// Declaring Structure for storing three values in each segment tree node    // A utility function to get the middle index from corner indexes.
class Node {
    public int pairs;
    public int open;
    public int closed;
 
    public Node() {
        pairs = 0;
        open = 0;
        closed = 0;
    }
}
 
class SegmentTree {
    public Node[] st;
    public int n;
    // A utility function to get the middle index from corner indexes.
    int getMid(int s, int e) {
        return s + (e - s) / 2;
    }
 // Returns Parent Node after merging its left and right child
    Node merge(Node leftChild, Node rightChild) {
        Node parentNode = new Node();
        int minMatched = Math.Min(leftChild.open, rightChild.closed);
        parentNode.pairs = leftChild.pairs + rightChild.pairs + minMatched;
        parentNode.open = leftChild.open + rightChild.open - minMatched;
        parentNode.closed = leftChild.closed + rightChild.closed - minMatched;
        return parentNode;
    }
    // A recursive function that constructs Segment Tree for string[ss..se].
    // si is index of current node in segment tree st
    void constructSTUtil(string str, int ss, int se, int si) {
         // If there is one element in string, store it in current node of
        // segment tree and return
         
        if (ss == se) {
                   // since it contains one element, pairs will be zero
            st[si].pairs = 0;
            // check whether that one element is opening bracket or not
            st[si].open = (str[ss] == '(') ? 1 : 0;
              // check whether that one element is closing bracket or not
        // If there are more than one elements, then recur for left and right
        // subtrees and store the relation of values in this node
            st[si].closed = (str[ss] == ')') ? 1 : 0;
            return;
        }
 
        // If there are more than one elements, then recur for left and right
        // subtrees and store the relation of values in this node
        int mid = getMid(ss, se);
        constructSTUtil(str, ss, mid, si * 2 + 1);
        constructSTUtil(str, mid + 1, se, si * 2 + 2);
 // Merge left and right child into the Parent Node
        st[si] = merge(st[si * 2 + 1], st[si * 2 + 2]);
    }
    // Function to construct segment tree from given string.
    // This function allocates memory for segment tree and calls
    // constructSTUtil() to fill the allocated memory
    public void constructST(string str) {
          // Allocate memory for segment tree
        // Height of segment tree
        int x = (int) Math.Ceiling(Math.Log(n) / Math.Log(2));
         // Maximum size of segment tree
        int max_size = 2 * (int) Math.Pow(2, x) - 1;
 
        st = new Node[max_size];
        for (int i = 0; i < max_size; i++)
            st[i] = new Node();
  // Fill the allocated memory st
        constructSTUtil(str, 0, n - 1, 0);
    }
    // A Recursive function to get the desired Maximum Sum Sub-Array,
    // The following are parameters of the function-
    // st     --> Pointer to segment tree
    // si --> Index of the segment tree Node
    // ss & se --> Starting and ending indexes of the segment
    // represented by current Node, i.e., tree[index]
    // qs & qe --> Starting and ending indexes of query range
    Node queryUtil(int ss, int se, int qs, int qe, int si) {
        // No overlap
        if (ss > qe || se < qs)
         // returns a Node for out of bounds condition
            return new Node();
 
        if (ss >= qs && se <= qe)
            return st[si];
 
        int mid = getMid(ss, se);
        Node left = queryUtil(ss, mid, qs, qe, si * 2 + 1);
        Node right = queryUtil(mid + 1, se, qs, qe, si * 2 + 2);
        return merge(left, right);
    }
  
      
    // The function to get the maximum length of correct bracket subsequence
    // for given range. The following are parameters of the function-
    // st --> Pointer to segment tree
    // qs & qe --> Starting and ending indexes of query range
    public int query(int qs, int qe) {
         
        Node node = queryUtil(0, n - 1, qs, qe, 0);
        return 2 * node.pairs;
    }
//Driver code
    public static void Main(string[] args) {
        string str = "())(())(())(";
        int n = str.Length;
        SegmentTree tree = new SegmentTree();
        tree.n = n;
        tree.constructST(str);
        int qs = 0;
        int qe = n - 1;
        Console.WriteLine("Maximum Length Correct Bracket Subsequence between " + qs + " and " + qe + " = " + tree.query(qs, qe));
         
        qs = 1;
        qe = 2;
        Console.WriteLine("Maximum Length Correct Bracket Subsequence between " + qs + " and " + qe + " = " + tree.query(qs, qe));
    }
}


Output

Maximum Length Correct Bracket Subsequence between 0 and 11 = 10
Maximum Length Correct Bracket Subsequence between 1 and 2 = 0

Time complexity: O(N*log N), where N is the size of the string
Auxiliary Space: O(N)