Open In App

Longest Common Extension / LCE using Segment Tree

Improve
Improve
Like Article
Like
Save
Share
Report

Prerequisites :

LCE(Set 1)

,

LCE(Set 2)

,

Suffix Array (n Log Log n)

,

Kasai’s algorithm

and

Segment Tree

The Longest Common Extension (LCE) problem considers a string

s

and computes, for each pair (L , R), the longest sub string of

s

that starts at both L and R. In LCE, in each of the query we have to answer the length of the longest common prefix starting at indexes L and R.

Example:

String

: “abbababba”

Queries:

LCE(1, 2), LCE(1, 6) and LCE(0, 5) Find the length of the Longest Common Prefix starting at index given as,

(1, 2), (1, 6) and (0, 5)

. The string highlighted

“green”

are the longest common prefix starting at index- L and R of the respective queries. We have to find the length of the longest common prefix starting at index-

(1, 2), (1, 6) and (0, 5)

.

Longest Common Extension

In this set we will discuss about the Segment Tree approach to solve the LCE problem. In

Set 2

, we saw that an LCE problem can be converted into a RMQ problem. To process the RMQ efficiently, we build a segment tree on the lcp array and then efficiently answer the LCE queries. To find low and high, we must have to compute the suffix array first and then from the suffix array we compute the inverse suffix array. We also need lcp array, hence we use

Kasai’s Algorithm to find lcp array from the suffix array

. Once the above things are done, we simply find the minimum value in lcp array from index low to high (as proved above) for each query. Without proving we will use the direct result (deduced after mathematical proofs)- LCE (L, R) = RMQ

lcp

(invSuff[R], invSuff[L]-1) The subscript lcp means that we have to perform RMQ on the lcp array and hence we will build a segment tree on the lcp array.

CPP
#include<bits/stdc++.h>
using namespace std;

// Structure to represent a query of form (L,R)
struct Query
{
    int L, R;
};

// Structure to store information of a suffix
struct suffix
{
    int index;  // To store the original index
    int rank[2]; // To store ranks and the next rank pair
};

// A utility function to get the minimum of two numbers
int minVal(int x, int y)
{
    return (x < y) ? x : y;
}

// A utility function to get the middle index from
// corner indexes.
int getMid(int s, int e)
{
    return s + (e - s) / 2;
}

/*  A recursive function to get the minimum value
    in a given range of array indexes. The following
    are parameters for this function.

    st    --> Pointer to the segment tree
    index --> Index of the current node in the segment
              tree. Initially, 0 is passed as the root
              is always at index 0
    ss & se  --> Starting and ending indexes of the
                  segment represented by the current
                  node, i.e., st[index]
    qs & qe  --> Starting and ending indexes of the query
                 range */
int RMQUtil(int *st, int ss, int se, int qs, int qe,
            int index)
{
    // If the segment of this node is a part of the given range,
    // then return the min of the segment
    if (qs <= ss && qe >= se)
        return st[index];

    // If the segment of this node is outside the given range
    if (se < qs || ss > qe)
        return INT_MAX;

    // If a part of this segment overlaps with the given range
    int mid = getMid(ss, se);
    return minVal(RMQUtil(st, ss, mid, qs, qe, 2 * index + 1),
                  RMQUtil(st, mid + 1, se, qs, qe, 2 * index + 2));
}

// Return the minimum of elements in the range from index qs
// (query start) to qe (query end). It mainly uses RMQUtil()
int RMQ(int *st, int n, int qs, int qe)
{
    // Check for erroneous input values
    if (qs < 0 || qe > n - 1 || qs > qe)
    {
        cout << "Invalid Input";
        return -1;
    }

    return RMQUtil(st, 0, n - 1, qs, qe, 0);
}

// A recursive function that constructs Segment Tree
// for array[ss..se]. si is the index of the current node in
// the segment tree st
int constructSTUtil(int arr[], int ss, int se, int *st,
                    int si)
{
    // If there is one element in the array, store it in
    // the current node of the segment tree and return
    if (ss == se)
    {
        st[si] = arr[ss];
        return arr[ss];
    }

    // If there are more than one element, then recur
    // for left and right subtrees and store the minimum
    // of two values in this node
    int mid = getMid(ss, se);
    st[si] = minVal(constructSTUtil(arr, ss, mid, st, si * 2 + 1),
                   constructSTUtil(arr, mid + 1, se, st, si * 2 + 2));
    return st[si];
}

/* Function to construct a segment tree from the given array.
   This function allocates memory for the segment tree and
   calls constructSTUtil() to fill the allocated memory */
int *constructST(int arr[], int n)
{
    // Allocate memory for the segment tree

    // Height of the segment tree
    int x = (int)(ceil(log2(n)));

    // Maximum size of the segment tree
    int max_size = 2 * (int)pow(2, x) - 1;

    int *st = new int[max_size];

    // Fill the allocated memory st
    constructSTUtil(arr, 0, n - 1, st, 0);

    // Return the constructed segment tree
    return st;
}

// A comparison function used by sort() to compare
// two suffixes. Compares two pairs, returns 1 if
// the first pair is smaller
int cmp(struct suffix a, struct suffix b)
{
    return (a.rank[0] == b.rank[0]) ?
               (a.rank[1] < b.rank[1] ? 1 : 0) :
               (a.rank[0] < b.rank[0] ? 1 : 0);
}

// This is the main function that takes a string
// 'txt' of size n as an argument, builds and returns
// the suffix array for the given string
vector<int> buildSuffixArray(string txt, int n)
{
    // A structure to store suffixes and their indexes
    struct suffix suffixes[n];

    // Store suffixes and their indexes in an array
    // of structures. The structure is needed to sort
    // the suffixes alphabetically and maintain their
    // old indexes while sorting
    for (int i = 0; i < n; i++)
    {
        suffixes[i].index = i;
        suffixes[i].rank[0] = txt[i] - 'a';
        suffixes[i].rank[1] = ((i + 1) < n) ?
                                 (txt[i + 1] - 'a') : -1;
    }

    // Sort the suffixes using the comparison function
    // defined above.
    sort(suffixes, suffixes + n, cmp);

    // At this point, all suffixes are sorted according to the first
    // 2 characters.  Let us sort suffixes according to the first 4
    // characters, then the first 8 and so on
    int ind[n]; // This array is needed to get the index
                // in suffixes[]
                // from the original index.  This mapping is needed to get
                // the next suffix.
    for (int k = 4; k < 2 * n; k = k * 2)
    {
        // Assigning rank and index values to the first suffix
        int rank = 0;
        int prev_rank = suffixes[0].rank[0];
        suffixes[0].rank[0] = rank;
        ind[suffixes[0].index] = 0;

        // Assigning rank to suffixes
        for (int i = 1; i < n; i++)
        {
            // If the first rank and next ranks are the same as
            // that of the previous suffix in the array, assign
            // the same new rank to this suffix
            if (suffixes[i].rank[0] == prev_rank &&
                suffixes[i].rank[1] == suffixes[i - 1].rank[1])
            {
                prev_rank = suffixes[i].rank[0];
                suffixes[i].rank[0] = rank;
            }
            else // Otherwise increment rank and assign
            {
                prev_rank = suffixes[i].rank[0];
                suffixes[i].rank[0] = ++rank;
            }
            ind[suffixes[i].index] = i;
        }

        // Assign the next rank to every suffix
        for (int i = 0; i < n; i++)
        {
            int nextindex = suffixes[i].index + k / 2;
            suffixes[i].rank[1] = (nextindex < n) ?
                                     suffixes[ind[nextindex]].rank[0] : -1;
        }

        // Sort the suffixes according to the first k characters
        sort(suffixes, suffixes + n, cmp);
    }

    // Store indexes of all sorted suffixes in the suffix array
    vector<int> suffixArr;
    for (int i = 0; i < n; i++)
        suffixArr.push_back(suffixes[i].index);

    // Return the suffix array
    return suffixArr;
}

/* To construct and return LCP */
vector<int> kasai(string txt, vector<int> suffixArr,
                  vector<int> &invSuff)
{
    int n = suffixArr.size();

    // To store the LCP array
    vector<int> lcp(n, 0);

    // Fill values in invSuff[]
    for (int i = 0; i < n; i++)
        invSuff[suffixArr[i]] = i;

    // Initialize the length of the previous LCP
    int k = 0;

    // Process all suffixes one by one starting from
    // the first suffix in txt[]
    for (int i = 0; i < n; i++)
    {
        /* If the current suffix is at n-1, then we don?t
           have the next substring to consider. So the LCP is not
           defined for this substring, we put zero. */
        if (invSuff[i] == n - 1)
        {
            k = 0;
            continue;
        }

        /* j contains the index of the next substring to
           be considered  to compare with the present
           substring, i.e., the next string in the suffix array */
        int j = suffixArr[invSuff[i] + 1];

        // Directly start matching from the k'th index as
        // at least k-1 characters will match
        while (i + k < n && j + k < n && txt[i + k] == txt[j + k])
            k++;

        lcp[invSuff[i]] = k; // LCP for the present suffix.

        // Deleting the starting character from the string.
        if (k > 0)
            k--;
    }

    // return the constructed LCP array
    return lcp;
}

// A utility function to find the longest common extension
// from index - L and index - R
int LCE(int *st, vector<int> lcp, vector<int> invSuff,
        int n, int L, int R)
{
    // Handle the corner case
    if (L == R)
        return (n - L);

    // Use the formula  -
    // LCE (L, R) = RMQ lcp (invSuff[R], invSuff[L]-1)
    return (RMQ(st, n, invSuff[R], invSuff[L] - 1));
}

// A function to answer queries of the longest common extension
void LCEQueries(string str, int n, Query q[],
                int m)
{
    // Build a suffix array
    vector<int> suffixArr = buildSuffixArray(str, str.length());

    // An auxiliary array to store the inverse of suffix array
    // elements. For example, if suffixArr[0] is 5, the
    // invSuff[5] would store 0.  This is used to get the next
    // suffix string from the suffix array.
    vector<int> invSuff(n, 0);

    // Build an LCP vector
    vector<int> lcp = kasai(str, suffixArr, invSuff);

    int lcpArr[n];
    // Convert to the LCP array
    for (int i = 0; i < n; i++)
        lcpArr[i] = lcp[i];

    // Build a segment tree from the LCP array
    int *st = constructST(lcpArr, n);

    for (int i = 0; i < m; i++)
    {
        int L = q[i].L;
        int R = q[i].R;

        cout << "LCE (" << L << ", " << R << ") = " << LCE(st, lcp, invSuff, n, L, R) << endl;
    }

    return;
}

// Driver Program to test above functions
int main()
{
    string str = "abbababba";
    int n = str.length();

    // LCA Queries to answer
    Query q[] = {{1, 2}, {1, 6}, {0, 5}};
    int m = sizeof(q) / sizeof(q[0]);

    LCEQueries(str, n, q, m);

    return (0);
}
Java
import java.util.*;

// Structure to represent a query of form (L,R)
class Query {
    int L, R;

    public Query(int L, int R) {
        this.L = L;
        this.R = R;
    }
}

// Structure to store information of a suffix
class Suffix {
    int index;  // To store original index
    int[] rank = new int[2]; // To store ranks and next rank pair
}

public class SuffixArrayLCE {
    // A utility function to get minimum of two numbers
    static int minVal(int x, int y) {
        return (x < y) ? x : y;
    }

    // A utility function to get the middle index from
    // corner indexes.
    static int getMid(int s, int e) {
        return s + (e - s) / 2;
    }

    /* A recursive function to get the minimum value
       in a given range of array indexes. The following
       are parameters for this function.

       st    --> Pointer to segment tree
       index --> Index of current node in the segment
                 tree. Initially 0 is passed as root
                 is always at index 0
       ss & se  --> Starting and ending indexes of the
                    segment represented by current
                    node, i.e., st[index]
       qs & qe  --> Starting and ending indexes of query
                    range */
    static int RMQUtil(int[] st, int ss, int se, int qs, int qe, int index) {
        // If segment of this node is a part of given range,
        // then return the min of the segment
        if (qs <= ss && qe >= se)
            return st[index];

        // If segment of this node is outside the given range
        if (se < qs || ss > qe)
            return Integer.MAX_VALUE;

        // If a part of this segment overlaps with the given
        // range
        int mid = getMid(ss, se);
        return minVal(RMQUtil(st, ss, mid, qs, qe, 2 * index + 1),
                RMQUtil(st, mid + 1, se, qs, qe, 2 * index + 2));
    }

    // Return minimum of elements in range from index qs
    // (query start) to qe (query end). It mainly uses RMQUtil()
    static int RMQ(int[] st, int n, int qs, int qe) {
        // Check for erroneous input values
        if (qs < 0 || qe > n - 1 || qs > qe) {
            System.out.println("Invalid Input");
            return -1;
        }

        return RMQUtil(st, 0, n - 1, qs, qe, 0);
    }

    // A recursive function that constructs Segment Tree
    // for array[ss..se]. si is index of the current node in
    // segment tree st
    static int constructSTUtil(int[] arr, int ss, int se, int[] st, int si) {
        // If there is one element in the array, store it in
        // the current node of the segment tree and return
        if (ss == se) {
            st[si] = arr[ss];
            return arr[ss];
        }

        // If there are more than one elements, then recur
        // for left and right subtrees and store the minimum
        // of two values in this node
        int mid = getMid(ss, se);
        st[si] = minVal(constructSTUtil(arr, ss, mid, st, si * 2 + 1),
                constructSTUtil(arr, mid + 1, se, st, si * 2 + 2));
        return st[si];
    }

    /* Function to construct a segment tree from the given array.
       This function allocates memory for the segment tree and
       calls constructSTUtil() to fill the allocated memory */
    static int[] constructST(int[] arr, int n) {
        // Allocate memory for the segment tree

        // Height of the segment tree
        int x = (int) (Math.ceil(Math.log(n) / Math.log(2)));

        // Maximum size of the segment tree
        int max_size = 2 * (int) Math.pow(2, x) - 1;

        int[] st = new int[max_size];

        // Fill the allocated memory st
        constructSTUtil(arr, 0, n - 1, st, 0);

        // Return the constructed segment tree
        return st;
    }

    // A comparison function used by sort() to compare
    // two suffixes. Compares two pairs, returns 1 if
    // the first pair is smaller
    static int cmp(Suffix a, Suffix b) {
        return (a.rank[0] == b.rank[0]) ?
                ((a.rank[1] < b.rank[1]) ? 1 : 0) :
                ((a.rank[0] < b.rank[0]) ? 1 : 0);
    }
  
    static int[] buildSuffixArray(String txt, int n) {
        Suffix[] suffixes = new Suffix[n];

        for (int i = 0; i < n; i++) {
            suffixes[i] = new Suffix();
            suffixes[i].index = i;
            suffixes[i].rank[0] = txt.charAt(i) - 'a';
            suffixes[i].rank[1] = (i + 1 < n) ? (txt.charAt(i + 1) - 'a') : -1;
        }

        Arrays.sort(suffixes, (a, b) -> {
            if (a.rank[0] == b.rank[0]) {
                return Integer.compare(a.rank[1], b.rank[1]);
            } else {
                return Integer.compare(a.rank[0], b.rank[0]);
            }
        });

        int[] ind = new int[n];
        for (int k = 4; k < 2 * n; k *= 2) {
            int rank = 0;
            int prevRank = suffixes[0].rank[0];
            suffixes[0].rank[0] = rank;
            ind[suffixes[0].index] = 0;

            for (int i = 1; i < n; i++) {
                if (suffixes[i].rank[0] == prevRank &&
                        suffixes[i].rank[1] == suffixes[i - 1].rank[1]) {
                    prevRank = suffixes[i].rank[0];
                    suffixes[i].rank[0] = rank;
                } else {
                    prevRank = suffixes[i].rank[0];
                    suffixes[i].rank[0] = ++rank;
                }
                ind[suffixes[i].index] = i;
            }

            for (int i = 0; i < n; i++) {
                int nextIndex = suffixes[i].index + k / 2;
                suffixes[i].rank[1] = (nextIndex < n) ?
                        suffixes[ind[nextIndex]].rank[0] : -1;
            }

            Arrays.sort(suffixes, (a, b) -> {
                if (a.rank[0] == b.rank[0]) {
                    return Integer.compare(a.rank[1], b.rank[1]);
                } else {
                    return Integer.compare(a.rank[0], b.rank[0]);
                }
            });
        }

        int[] suffixArr = new int[n];
        for (int i = 0; i < n; i++)
            suffixArr[i] = suffixes[i].index;

        return suffixArr;
    }    
  
     static List<Integer> kasai(String txt, int[] suffixArr, int[] invSuff) {
        int n = suffixArr.length;
        List<Integer> lcp = new ArrayList<>(n);
        for (int i = 0; i < n; i++)
            lcp.add(0);

        for (int i = 0; i < n; i++)
            invSuff[suffixArr[i]] = i;

        int k = 0;

        for (int i = 0; i < n; i++) {
            if (invSuff[i] == n - 1) {
                k = 0;
                continue;
            }

            int j = suffixArr[invSuff[i] + 1];

            while (i + k < n && j + k < n && txt.charAt(i + k) == txt.charAt(j + k))
                k++;

            lcp.set(invSuff[i], k);

            if (k > 0)
                k--;
        }

        return lcp;
    }
  
  static int LCE(int[] st, List<Integer> lcp, int[] invSuff, int n, int L, int R) {
        if (L == R)
            return (n - L);

        return (RMQ(st, n, invSuff[R], invSuff[L] - 1));
    }
  
  static void LCEQueries(String str, int n, Query[] q) {
        // Build a suffix array
        int[] suffixArr = buildSuffixArray(str, str.length());

        // An auxiliary array to store inverse of suffix array
        // elements. For example, if suffixArr[0] is 5, the
        // invSuff[5] would store 0. This is used to get the next
        // suffix string from the suffix array.
        int[] invSuff = new int[n];

        // Build an lcp vector
        List<Integer> lcp = kasai(str, suffixArr, invSuff);

        int[] lcpArr = new int[n];
        // Convert to lcp array
        for (int i = 0; i < n; i++)
            lcpArr[i] = lcp.get(i);

        // Build segment tree from lcp array
        int[] st = constructST(lcpArr, n);

        for (int i = 0; i < q.length; i++) {
            int L = q[i].L;
            int R = q[i].R;

            System.out.printf("LCE (%d, %d) = %d\n", L, R,
                    LCE(st, lcp, invSuff, n, L, R));
        }
    }
  
  //Code by Dibyabrata Panja
 public static void main(String[] args) {
        String str = "abbababba";
        int n = str.length();

        // LCA Queries to answer
       // LCA Queries to answer
        Query[] q = {new Query(1, 2), new Query(1, 6), new Query(0, 5)};

        q[0].L = 1; q[0].R = 2;
        q[1].L = 1; q[1].R = 6;
        q[2].L = 0; q[2].R = 5;

        LCEQueries(str, n, q);
    }
}
C#
using System;
using System.Collections.Generic;
using System.Linq;

// Structure to represent a query of form (L,R)
class Query
{
    public int L, R;

    public Query(int L, int R)
    {
        this.L = L;
        this.R = R;
    }
}

// Structure to store information of a suffix
class Suffix
{
    public int index;  // To store original index
    public int[] rank = new int[2]; // To store ranks and next rank pair
}

public class SuffixArrayLCE
{
    // A utility function to get minimum of two numbers
    static int minVal(int x, int y)
    {
        return (x < y) ? x : y;
    }

    // A utility function to get the middle index from
    // corner indexes.
    static int getMid(int s, int e)
    {
        return s + (e - s) / 2;
    }

    /* A recursive function to get the minimum value
       in a given range of array indexes. The following
       are parameters for this function.

       st    --> Pointer to segment tree
       index --> Index of current node in the segment
                 tree. Initially 0 is passed as root
                 is always at index 0
       ss & se  --> Starting and ending indexes of the
                    segment represented by current
                    node, i.e., st[index]
       qs & qe  --> Starting and ending indexes of query
                    range */
    static int RMQUtil(int[] st, int ss, int se, int qs, int qe, int index)
    {
        // If segment of this node is a part of given range,
        // then return the min of the segment
        if (qs <= ss && qe >= se)
            return st[index];

        // If segment of this node is outside the given range
        if (se < qs || ss > qe)
            return int.MaxValue;

        // If a part of this segment overlaps with the given
        // range
        int mid = getMid(ss, se);
        return minVal(RMQUtil(st, ss, mid, qs, qe, 2 * index + 1),
                RMQUtil(st, mid + 1, se, qs, qe, 2 * index + 2));
    }

    // Return minimum of elements in range from index qs
    // (query start) to qe (query end). It mainly uses RMQUtil()
    static int RMQ(int[] st, int n, int qs, int qe)
    {
        // Check for erroneous input values
        if (qs < 0 || qe > n - 1 || qs > qe)
        {
            Console.WriteLine("Invalid Input");
            return -1;
        }

        return RMQUtil(st, 0, n - 1, qs, qe, 0);
    }

    // A recursive function that constructs Segment Tree
    // for array[ss..se]. si is index of the current node in
    // segment tree st
    static int constructSTUtil(int[] arr, int ss, int se, int[] st, int si)
    {
        // If there is one element in the array, store it in
        // the current node of the segment tree and return
        if (ss == se)
        {
            st[si] = arr[ss];
            return arr[ss];
        }

        // If there are more than one elements, then recur
        // for left and right subtrees and store the minimum
        // of two values in this node
        int mid = getMid(ss, se);
        st[si] = minVal(constructSTUtil(arr, ss, mid, st, si * 2 + 1),
                constructSTUtil(arr, mid + 1, se, st, si * 2 + 2));
        return st[si];
    }

    /* Function to construct a segment tree from the given array.
       This function allocates memory for the segment tree and
       calls constructSTUtil() to fill the allocated memory */
    static int[] constructST(int[] arr, int n)
    {
        // Allocate memory for the segment tree

        // Height of the segment tree
        int x = (int)(Math.Ceiling(Math.Log(n) / Math.Log(2)));

        // Maximum size of the segment tree
        int max_size = 2 * (int)Math.Pow(2, x) - 1;

        int[] st = new int[max_size];

        // Fill the allocated memory st
        constructSTUtil(arr, 0, n - 1, st, 0);

        // Return the constructed segment tree
        return st;
    }

    // A comparison function used by sort() to compare
    // two suffixes. Compares two pairs, returns 1 if
    // the first pair is smaller
    static int cmp(Suffix a, Suffix b)
    {
        return (a.rank[0] == b.rank[0]) ?
                ((a.rank[1] < b.rank[1]) ? 1 : 0) :
                ((a.rank[0] < b.rank[0]) ? 1 : 0);
    }

    static int[] buildSuffixArray(string txt, int n)
    {
        Suffix[] suffixes = new Suffix[n];

        for (int i = 0; i < n; i++)
        {
            suffixes[i] = new Suffix();
            suffixes[i].index = i;
            suffixes[i].rank[0] = txt[i] - 'a';
            suffixes[i].rank[1] = (i + 1 < n) ? (txt[i + 1] - 'a') : -1;
        }

        Array.Sort(suffixes, (a, b) =>
        {
            if (a.rank[0] == b.rank[0])
            {
                return a.rank[1].CompareTo(b.rank[1]);
            }
            else
            {
                return a.rank[0].CompareTo(b.rank[0]);
            }
        });

        int[] ind = new int[n];
        for (int k = 4; k < 2 * n; k *= 2)
        {
            int rank = 0;
            int prevRank = suffixes[0].rank[0];
            suffixes[0].rank[0] = rank;
            ind[suffixes[0].index] = 0;

            for (int i = 1; i < n; i++)
            {
                if (suffixes[i].rank[0] == prevRank &&
                        suffixes[i].rank[1] == suffixes[i - 1].rank[1])
                {
                    prevRank = suffixes[i].rank[0];
                    suffixes[i].rank[0] = rank;
                }
                else
                {
                    prevRank = suffixes[i].rank[0];
                    suffixes[i].rank[0] = ++rank;
                }
                ind[suffixes[i].index] = i;
            }

            for (int i = 0; i < n; i++)
            {
                int nextIndex = suffixes[i].index + k / 2;
                suffixes[i].rank[1] = (nextIndex < n) ?
                        suffixes[ind[nextIndex]].rank[0] : -1;
            }

            Array.Sort(suffixes, (a, b) =>
            {
                if (a.rank[0] == b.rank[0])
                {
                    return a.rank[1].CompareTo(b.rank[1]);
                }
                else
                {
                    return a.rank[0].CompareTo(b.rank[0]);
                }
            });
        }

        int[] suffixArr = new int[n];
        for (int i = 0; i < n; i++)
            suffixArr[i] = suffixes[i].index;

        return suffixArr;
    }

    static List<int> kasai(string txt, int[] suffixArr, int[] invSuff)
    {
        int n = suffixArr.Length;
        List<int> lcp = new List<int>(n);
        for (int i = 0; i < n; i++)
            lcp.Add(0);

        for (int i = 0; i < n; i++)
            invSuff[suffixArr[i]] = i;

        int k = 0;

        for (int i = 0; i < n; i++)
        {
            if (invSuff[i] == n - 1)
            {
                k = 0;
                continue;
            }

            int j = suffixArr[invSuff[i] + 1];

            while (i + k < n && j + k < n && txt[i + k] == txt[j + k])
                k++;

            lcp[invSuff[i]] = k;

            if (k > 0)
                k--;
        }

        return lcp;
    }

    static int LCE(int[] st, List<int> lcp, int[] invSuff, int n, int L, int R)
    {
        if (L == R)
            return (n - L);

        return (RMQ(st, n, invSuff[R], invSuff[L] - 1));
    }

    static void LCEQueries(string str, int n, Query[] q)
    {
        // Build a suffix array
        int[] suffixArr = buildSuffixArray(str, str.Length);

        // An auxiliary array to store the inverse of suffix array
        // elements. For example, if suffixArr[0] is 5, the
        // invSuff[5] would store 0. This is used to get the next
        // suffix string from the suffix array.
        int[] invSuff = new int[n];

        // Build an lcp vector
        List<int> lcp = kasai(str, suffixArr, invSuff);

        int[] lcpArr = lcp.ToArray();
        // Build segment tree from lcp array
        int[] st = constructST(lcpArr, n);

        for (int i = 0; i < q.Length; i++)
        {
            int L = q[i].L;
            int R = q[i].R;

            Console.WriteLine($"LCE ({L}, {R}) = {LCE(st, lcp, invSuff, n, L, R)}");
        }
    }

    // Code by Dibyabrata Panja
    public static void Main(string[] args)
    {
        string str = "abbababba";
        int n = str.Length;

        // LCA Queries to answer
        Query[] q = { new Query(1, 2), new Query(1, 6), new Query(0, 5) };

        q[0].L = 1; q[0].R = 2;
        q[1].L = 1; q[1].R = 6;
        q[2].L = 0; q[2].R = 5;

        LCEQueries(str, n, q);
    }
}
Javascript
class Query {
    constructor(L, R) {
        this.L = L;
        this.R = R;
    }
}

class Suffix {
    constructor() {
        this.index = 0;
        this.rank = [0, 0];
    }
}

function minVal(x, y) {
    return x < y ? x : y;
}

function getMid(s, e) {
    return s + Math.floor((e - s) / 2);
}

function RMQUtil(st, ss, se, qs, qe, index) {
    if (qs <= ss && qe >= se)
        return st[index];

    if (se < qs || ss > qe)
        return Infinity;

    const mid = getMid(ss, se);
    return minVal(
        RMQUtil(st, ss, mid, qs, qe, 2 * index + 1),
        RMQUtil(st, mid + 1, se, qs, qe, 2 * index + 2)
    );
}

function RMQ(st, n, qs, qe) {
    if (qs < 0 || qe > n - 1 || qs > qe) {
        console.log("Invalid Input");
        return -1;
    }

    return RMQUtil(st, 0, n - 1, qs, qe, 0);
}

function constructSTUtil(arr, ss, se, st, si) {
    if (ss === se) {
        st[si] = arr[ss];
        return arr[ss];
    }

    const mid = getMid(ss, se);
    st[si] = minVal(
        constructSTUtil(arr, ss, mid, st, si * 2 + 1),
        constructSTUtil(arr, mid + 1, se, st, si * 2 + 2)
    );
    return st[si];
}

function constructST(arr, n) {
    const x = Math.ceil(Math.log2(n));
    const max_size = 2 * 2**x - 1;
    const st = new Array(max_size).fill(0);

    constructSTUtil(arr, 0, n - 1, st, 0);
    return st;
}

function cmp(a, b) {
    return a.rank[0] === b.rank[0]
        ? a.rank[1] - b.rank[1]
        : a.rank[0] - b.rank[0];
}

function buildSuffixArray(txt, n) {
    const suffixes = new Array(n);

    for (let i = 0; i < n; i++) {
        suffixes[i] = new Suffix();
        suffixes[i].index = i;
        suffixes[i].rank[0] = txt[i].charCodeAt(0) - 'a'.charCodeAt(0);
        suffixes[i].rank[1] = (i + 1 < n) ? (txt[i + 1].charCodeAt(0) - 'a'.charCodeAt(0)) : -1;
    }

    suffixes.sort((a, b) => cmp(a, b));

    const ind = new Array(n).fill(0);

    for (let k = 4; k < 2 * n; k *= 2) {
        let rank = 0;
        let prevRank = suffixes[0].rank[0];
        suffixes[0].rank[0] = rank;
        ind[suffixes[0].index] = 0;

        for (let i = 1; i < n; i++) {
            if (suffixes[i].rank[0] === prevRank &&
                suffixes[i].rank[1] === suffixes[i - 1].rank[1]) {
                prevRank = suffixes[i].rank[0];
                suffixes[i].rank[0] = rank;
            } else {
                prevRank = suffixes[i].rank[0];
                suffixes[i].rank[0] = ++rank;
            }
            ind[suffixes[i].index] = i;
        }

        for (let i = 0; i < n; i++) {
            const nextIndex = suffixes[i].index + k / 2;
            suffixes[i].rank[1] = (nextIndex < n) ?
                suffixes[ind[nextIndex]].rank[0] : -1;
        }

        suffixes.sort((a, b) => cmp(a, b));
    }

    const suffixArr = suffixes.map(suffix => suffix.index);
    return suffixArr;
}

function kasai(txt, suffixArr, invSuff) {
    const n = suffixArr.length;
    const lcp = new Array(n).fill(0);

    for (let i = 0; i < n; i++)
        invSuff[suffixArr[i]] = i;

    let k = 0;

    for (let i = 0; i < n; i++) {
        if (invSuff[i] === n - 1) {
            k = 0;
            continue;
        }

        const j = suffixArr[invSuff[i] + 1];

        while (i + k < n && j + k < n && txt[i + k] === txt[j + k])
            k++;

        lcp[invSuff[i]] = k;

        if (k > 0)
            k--;
    }

    return lcp;
}

function LCE(st, lcp, invSuff, n, L, R) {
    if (L === R)
        return n - L;

    return RMQ(st, n, invSuff[R], invSuff[L] - 1);
}

function LCEQueries(str, n, queries) {
    // Build a suffix array
    const suffixArr = buildSuffixArray(str, str.length);

    // An auxiliary array to store the inverse of the suffix array
    // elements. For example, if suffixArr[0] is 5, then
    // invSuff[5] would store 0. This is used to get the next
    // suffix string from the suffix array.
    const invSuff = new Array(n).fill(0);

    // Build an LCP vector
    const lcp = kasai(str, suffixArr, invSuff);

    const lcpArr = [...lcp];
    // Build segment tree from LCP array
    const st = constructST(lcpArr, n);

    for (let i = 0; i < queries.length; i++) {
        const L = queries[i].L;
        const R = queries[i].R;

        console.log(`LCE (${L}, ${R}) = ${LCE(st, lcp, invSuff, n, L, R)}`);
    }
}


const str = "abbababba";
const n = str.length;

// LCA Queries to answer
const queries = [new Query(1, 2), new Query(1, 6), new Query(0, 5)];

queries[0].L = 1; queries[0].R = 2;
queries[1].L = 1; queries[1].R = 6;
queries[2].L = 0; queries[2].R = 5;

LCEQueries(str, n, queries);
Python3
import math

# Structure to represent a query of form (L,R)
class Query:
    def __init__(self, L, R):
        self.L = L
        self.R = R

# Structure to store information of a suffix
class Suffix:
    def __init__(self):
        self.index = 0  # To store the original index
        self.rank = [0, 0]  # To store ranks and the next rank pair

# A utility function to get the minimum of two numbers
def minVal(x, y):
    return x if x < y else y

# A utility function to get the middle index from corner indexes.
def getMid(s, e):
    return s + (e - s) // 2

# A recursive function to get the minimum value in a given range of array indexes
def RMQUtil(st, ss, se, qs, qe, index):
    # If the segment of this node is a part of the given range, then return the min of the segment
    if qs <= ss and qe >= se:
        return st[index]

    # If the segment of this node is outside the given range
    if se < qs or ss > qe:
        return math.inf

    # If a part of this segment overlaps with the given range
    mid = getMid(ss, se)
    return minVal(RMQUtil(st, ss, mid, qs, qe, 2 * index + 1), RMQUtil(st, mid + 1, se, qs, qe, 2 * index + 2))

# Return the minimum of elements in the range from index qs (query start) to qe (query end). It mainly uses RMQUtil()
def RMQ(st, n, qs, qe):
    # Check for erroneous input values
    if qs < 0 or qe > n - 1 or qs > qe:
        print("Invalid Input")
        return -1

    return RMQUtil(st, 0, n - 1, qs, qe, 0)

# A recursive function that constructs Segment Tree for array[ss..se]. si is the index of the current node in the segment tree st
def constructSTUtil(arr, ss, se, st, si):
    # If there is one element in the array, store it in the current node of the segment tree and return
    if ss == se:
        st[si] = arr[ss]
        return arr[ss]

    # If there are more than one element, then recur for left and right subtrees and store the minimum of two values in this node
    mid = getMid(ss, se)
    st[si] = minVal(constructSTUtil(arr, ss, mid, st, si * 2 + 1), constructSTUtil(arr, mid + 1, se, st, si * 2 + 2))
    return st[si]

# Function to construct a segment tree from the given array.
def constructST(arr, n):
    # Height of the segment tree
    x = math.ceil(math.log2(n))

    # Maximum size of the segment tree
    max_size = 2 * (2 ** x) - 1

    st = [0] * max_size

    # Fill the allocated memory st
    constructSTUtil(arr, 0, n - 1, st, 0)

    # Return the constructed segment tree
    return st

# A comparison function used by sort() to compare two suffixes. Compares two pairs, returns 1 if the first pair is smaller
def cmp(a, b):
    if a.rank[0] == b.rank[0]:
        return 1 if a.rank[1] < b.rank[1] else 0
    else:
        return 1 if a.rank[0] < b.rank[0] else 0

# This is the main function that takes a string 'txt' of size n as an argument, builds and returns the suffix array for the given string
def buildSuffixArray(txt, n):
    # A structure to store suffixes and their indexes
    suffixes = [Suffix() for _ in range(n)]

    # Store suffixes and their indexes in an array of structures. The structure is needed to sort the suffixes alphabetically and maintain their old indexes while sorting
    for i in range(n):
        suffixes[i].index = i
        suffixes[i].rank[0] = ord(txt[i]) - ord('a')
        suffixes[i].rank[1] = ord(txt[i + 1]) - ord('a') if i + 1 < n else -1

    # Sort the suffixes using the comparison function defined above.
    suffixes.sort(key=lambda x: (x.rank[0], x.rank[1]))

    # At this point, all suffixes are sorted according to the first 2 characters.
    # Let us sort suffixes according to the first 4 characters, then the first 8 and so on
    ind = [0] * n  # This array is needed to get the index in suffixes[] from the original index. This mapping is needed to get the next suffix.
    k = 4
    while k < 2 * n:
        # Assigning rank and index values to the first suffix
        rank = 0
        prev_rank = suffixes[0].rank[0]
        suffixes[0].rank[0] = rank
        ind[suffixes[0].index] = 0

        # Assigning rank to suffixes
        for i in range(1, n):
            if suffixes[i].rank[0] == prev_rank and suffixes[i].rank[1] == suffixes[i - 1].rank[1]:
                prev_rank = suffixes[i].rank[0]
                suffixes[i].rank[0] = rank
            else:
                prev_rank = suffixes[i].rank[0]
                rank += 1
                suffixes[i].rank[0] = rank
            ind[suffixes[i].index] = i

        # Assign the next rank to every suffix
        for i in range(n):
            nextindex = suffixes[i].index + k // 2
            suffixes[i].rank[1] = suffixes[ind[nextindex]].rank[0] if nextindex < n else -1

        # Sort the suffixes according to the first k characters
        suffixes.sort(key=lambda x: (x.rank[0], x.rank[1]))
        k *= 2

    # Store indexes of all sorted suffixes in the suffix array
    suffixArr = [0] * n
    for i in range(n):
        suffixArr[i] = suffixes[i].index

    # Return the suffix array
    return suffixArr

# To construct and return LCP
def kasai(txt, suffixArr, invSuff):
    n = len(suffixArr)

    # To store the LCP array
    lcp = [0] * n

    # Fill values in invSuff[]
    for i in range(n):
        invSuff[suffixArr[i]] = i

    # Initialize the length of the previous LCP
    k = 0

    # Process all suffixes one by one starting from the first suffix in txt[]
    for i in range(n):
        # If the current suffix is at n-1, then we don't have the next substring to consider.
        # So the LCP is not defined for this substring, we put zero.
        if invSuff[i] == n - 1:
            k = 0
            continue

        # j contains the index of the next substring to be considered to compare with the present substring,
        # i.e., the next string in the suffix array
        j = suffixArr[invSuff[i] + 1]

        # Directly start matching from the k'th index as at least k-1 characters will match
        while i + k < n and j + k < n and txt[i + k] == txt[j + k]:
            k += 1

        lcp[invSuff[i]] = k  # LCP for the present suffix.

        # Deleting the starting character from the string.
        if k > 0:
            k -= 1

    # return the constructed LCP array
    return lcp

# A utility function to find the longest common extension from index - L and index - R
def LCE(st, lcp, invSuff, n, L, R):
    # Handle the corner case
    if L == R:
        return (n - L)

    # Use the formula - LCE (L, R) = RMQ lcp (invSuff[R], invSuff[L]-1)
    return (RMQ(st, n, invSuff[R], invSuff[L] - 1))

# A function to answer queries of the longest common extension
def LCEQueries(txt, n, q, m):
    # Build a suffix array
    suffixArr = buildSuffixArray(txt, len(txt))

    # An auxiliary array to store the inverse of suffix array elements.
    invSuff = [0] * n

    # Build an LCP vector
    lcp = kasai(txt, suffixArr, invSuff)

    lcpArr = lcp

    # Build a segment tree from the LCP array
    st = constructST(lcpArr, n)

    for i in range(m):
        L = q[i].L
        R = q[i].R

        print("LCE (", L, ", ", R, ") = ", LCE(st, lcp, invSuff, n, L, R), sep='')

# Driver Program to test above functions
if __name__ == "__main__":
    txt = "abbababba"
    n = len(txt)

    # LCA Queries to answer
    q = [Query(1, 2), Query(1, 6), Query(0, 5)]
    m = len(q)

    LCEQueries(txt, n, q, m)

Output:

LCE (1, 2) = 1
LCE (1, 6) = 3
LCE (0, 5) = 4


Time Complexity :

To construct the lcp and the suffix array it takes O(N.logN) time. To answer each query it takes O(log N). Hence the overall time complexity is O(N.logN + Q.logN)). Although we can construct the lcp array and the suffix array in O(N) time using other algorithms. where, Q = Number of LCE Queries. N = Length of the input string.

Auxiliary Space :

We use O(N) auxiliary space to store lcp, suffix and inverse suffix arrays and segment tree.

Comparison of Performances:

We have seen three algorithm to compute the length of the LCE.

Set 1 :

Naive Method [O(N.Q)]

Set 2 :

RMQ-Direct Minimum Method [O(N.logN + Q. (|invSuff[R] – invSuff[L]|))]

Set 3 :

Segment Tree Method [O(N.logN + Q.logN))] invSuff[] = Inverse suffix array of the input string. From the asymptotic time complexity it seems that the Segment Tree method is most efficient and the other two are very inefficient. But when it comes to practical world this is not the case. If we plot a graph between time vs log((|invSuff[R] – invSuff[L]|) for typical files having random strings for various runs, then the result is as shown below.

LCE

The above graph is taken from

this

reference. The tests were run on 25 files having random strings ranging from 0.7 MB to 2 GB. The exact sizes of string is not known but obviously a 2 GB file has a lot of characters in it. This is because 1 character = 1 byte. So, about 1000 characters equal 1 kilobyte. If a page has 2000 characters on it (a reasonable average for a double-spaced page), then it will take up 2K (2 kilobytes). That means it will take about 500 pages of text to equal one megabyte. Hence 2 gigabyte = 2000 megabyte = 2000*500 = 10,00,000 pages of text ! From the above graph it is clear that the Naive Method (discussed in Set 1) performs the best (better than Segment Tree Method). This is surprising as the asymptotic time complexity of Segment Tree Method is much lesser than that of the Naive Method. In fact, the naive method is generally 5-6 times faster than the Segment Tree Method on typical files having random strings. Also not to forget that the Naive Method is an in-place algorithm, thus making it the most desirable algorithm to compute LCE . The bottom-line is that the naive method is the most optimal choice for answering the LCE queries when it comes to average-case performance. Such kind of thinks rarely happens in Computer Science when a faster-looking algorithm gets beaten by a less efficient one in practical tests. What we learn is that the although asymptotic analysis is one of the most effective way to compare two algorithms on paper, but in practical uses sometimes things may happen the other way round.

References:

http://www.sciencedirect.com/science/article/pii/S1570866710000377

If you like GeeksforGeeks and would like to contribute, you can also write an article using

write.geeksforgeeks.org

or mail your article to review-team@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks. Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.



Last Updated : 23 Mar, 2024
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads