Open In App

Longest Common Extension / LCE using RMQ

Prerequisites : 

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).

In Set 1, we explained about the naive method to find the length of the LCE of a string on many queries. In this set we will show how a LCE problem can be reduced to a RMQ problem, hence decreasing the asymptotic time complexity of the naive method.

Reduction of LCE to RMQ

Let the input string be S and queries be of the formLCE(L, R). Let the suffix array for s be Suff[] and the lcp array be lcp[].
The longest common extension between two suffixes SL and SR of S can be obtained from the lcp array in the following way. 

Proof: Let SL = SL…SL+C…sn and SR = SR…SR+c…sn, and let c be the longest common extension of SL and SR(i.e. SL…SL+C-1 = sn…SR+c-1). We assume that the string S has a sentinel character so that no suffix of S is a prefix of any other suffix of S but itself.

Therefore we have c = lcp[i]
Thus we have reduced our longest common extension query to a range minimum-query over a range in lcp.

Algorithm

The minimum value is the length of the LCE for that query. 

Implementation 




// A C++ Program to find the length of longest common
// extension using Direct Minimum Algorithm
#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 original index
    int rank[2]; // To store ranks and next rank pair
};
  
// A utility function to get minimum of two numbers
int minVal(int x, int y) { return (x < y) ? x : y; }
  
// A utility function to get minimum of two numbers
int maxVal(int x, int y) { return (x > y) ? x : y; }
  
// A comparison function used by sort() to compare
// two suffixes Compares two pairs, returns 1 if
// 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])
               : (a.rank[0] < b.rank[0]);
}
  
// This is the main function that takes a string 'txt'
// of size n as an argument, builds and return 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 his point, all suffixes are sorted according
    // to first 2 characters.  Let us sort suffixes
    // according to first 4/ characters, then first 8
    // and so on
  
    // This array is needed to get the index in suffixes[]
    // from original index.  This mapping is needed to get
    // next suffix.
    int ind[n];
  
    for (int k = 4; k < 2 * n; k = k * 2) {
        // Assigning rank and index values to 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 first rank and next ranks are same as
            // that of previous/ suffix in 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 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 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 LCP array
    vector<int> lcp(n, 0);
  
    // Fill values in invSuff[]
    for (int i = 0; i < n; i++)
        invSuff[suffixArr[i]] = i;
  
    // Initialize length of previous LCP
    int k = 0;
  
    // Process all suffixes one by one starting from
    // first suffix in txt[]
    for (int i = 0; i < n; i++) {
        /* If the current suffix is at n-1, then we don’t
           have next substring to consider. So lcp is not
           defined for this substring, we put zero. */
        if (invSuff[i] == n - 1) {
            k = 0;
            continue;
        }
  
        /* j contains index of the next substring to
           be considered  to compare with the present
           substring, i.e., next string in suffix array */
        int j = suffixArr[invSuff[i] + 1];
  
        // Directly start matching from 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 longest common extension
// from index - L and index - R
int LCE(vector<int> lcp, vector<int> invSuff, int n, int L,
        int R)
{
    // Handle the corner case
    if (L == R)
        return (n - L);
  
    int low = minVal(invSuff[L], invSuff[R]);
    int high = maxVal(invSuff[L], invSuff[R]);
  
    int length = lcp[low];
  
    for (int i = low + 1; i < high; i++) {
        if (lcp[i] < length)
            length = lcp[i];
    }
  
    return (length);
}
  
// A function to answer queries of 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 inverse of suffix array
    // elements. For example if suffixArr[0] is 5, the
    // invSuff[5] would store 0.  This is used to get next
    // suffix string from suffix array.
    vector<int> invSuff(n, 0);
  
    // Build a lcp vector
    vector<int> lcp = kasai(str, suffixArr, invSuff);
  
    for (int i = 0; i < m; i++) {
        int L = q[i].L;
        int R = q[i].R;
  
        printf("LCE (%d, %d) = %d\n", L, R,
               LCE(lcp, invSuff, n, L, R));
    }
  
    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);
}




import java.util.Arrays;
  
// 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 implements Comparable<Suffix> {
    int index;
    int[] rank = new int[2];
  
    public Suffix(int index) { this.index = index; }
  
    @Override public int compareTo(Suffix other)
    {
        // Compare two suffixes based on their ranks
        return (this.rank[0] == other.rank[0])
            ? Integer.compare(this.rank[1], other.rank[1])
            : Integer.compare(this.rank[0], other.rank[0]);
    }
}
  
public class LongestCommonExtension {
  
    // A utility function to get the minimum of two numbers
    static int minVal(int x, int y)
    {
        return (x < y) ? x : y;
    }
  
    // A utility function to get the maximum of two numbers
    static int maxVal(int x, int y)
    {
        return (x > y) ? x : y;
    }
  
    // A comparison function used by Arrays.sort() to
    // compare two suffixes Compares two pairs, returns 1 if
    // the first pair is smaller
    static int compare(Suffix a, Suffix b)
    {
        return (a.rank[0] == b.rank[0])
            ? Integer.compare(a.rank[1], b.rank[1])
            : Integer.compare(a.rank[0], b.rank[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
    static int[] buildSuffixArray(String txt)
    {
        int n = txt.length();
        Suffix[] suffixes = new Suffix[n];
  
        for (int i = 0; i < n; i++) {
            suffixes[i] = new Suffix(i);
            suffixes[i].rank[0] = txt.charAt(i) - 'a';
            suffixes[i].rank[1]
                = (i + 1) < n ? txt.charAt(i + 1) - 'a'
                              : -1;
        }
  
        // Sort the suffixes using the comparison function
        Arrays.sort(suffixes);
  
        int[] ind = new int[n];
  
        for (int k = 4; k < 2 * n; k = k * 2) {
            int rank = 0;
            int prev_rank = 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] == prev_rank
                    && 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];
                    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;
            }
  
            // Sort the suffixes according to the first k
            // characters
            Arrays.sort(suffixes);
        }
  
        int[] suffixArr = new int[n];
        for (int i = 0; i < n; i++) {
            suffixArr[i] = suffixes[i].index;
        }
  
        return suffixArr;
    }
  
    // To construct and return LCP
    static int[] kasai(String txt, int[] suffixArr)
    {
        int n = suffixArr.length;
        int[] lcp = new int[n];
        int[] invSuff = new int[n];
  
        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[invSuff[i]] = k;
  
            if (k > 0) {
                k--;
            }
        }
  
        return lcp;
    }
  
    // A utility function to find the longest common
    // extension from index - L and index - R
    static int LCE(int[] lcp, int[] invSuff, int n, int L,
                   int R)
    {
        if (L == R) {
            return n - L;
        }
  
        int low = minVal(invSuff[L], invSuff[R]);
        int high = maxVal(invSuff[L], invSuff[R]);
  
        int length = lcp[low];
  
        for (int i = low + 1; i < high; i++) {
            if (lcp[i] < length) {
                length = lcp[i];
            }
        }
  
        return length;
    }
  
    // A function to answer queries of the longest common
    // extension
    static void LCEQueries(String txt, int[] suffixArr,
                           Query[] q)
    {
        int n = txt.length();
        int[] invSuff = new int[n];
  
        for (int i = 0; i < n; i++) {
            invSuff[suffixArr[i]] = i;
        }
  
        int[] lcp = kasai(txt, suffixArr);
  
        for (Query query : q) {
            int L = query.L;
            int R = query.R;
  
            System.out.println(
                "LCE (" + L + ", " + R
                + ") = " + LCE(lcp, invSuff, n, L, R));
        }
    }
  
    public static void main(String[] args)
    {
        String txt = "abbababba";
        int n = txt.length();
  
        Query[] queries
            = { new Query(1, 2), new Query(1, 6),
                new Query(0, 5) };
  
        int[] suffixArr = buildSuffixArray(txt);
  
        LCEQueries(txt, suffixArr, queries);
    }
}




# Class to represent a query of form (L, R)
class Query:
    def __init__(self, L, R):
        self.L = L
        self.R = R
  
# Class to store information of a suffix
class Suffix:
    def __init__(self, index):
        self.index = index
        self.rank = [0, 0# Initial ranks of the suffixes
  
# Function to build the suffix array for a given string
def build_suffix_array(txt):
    n = len(txt)
    # Create a list of suffixes
    suffixes = [Suffix(i) for i in range(n)]
    for i in range(n):
        # Rank of suffixes using first two characters
        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
    suffixes.sort(key=lambda x: (x.rank[0], x.rank[1]))
    ind = [0]*# To store indices of suffixes
    k = 4
    while k < 2*n:
        rank = 0
        prev_rank = suffixes[0].rank[0]
        suffixes[0].rank[0] = rank
        ind[suffixes[0].index] = 0
        for i in range(1, n):
            # If first rank and next ranks are same as that of previous
            # suffix in array, assign the same new rank to this suffix
            if suffixes[i].rank[0] == prev_rank and suffixes[i].rank[1] == suffixes[i - 1].rank[1]:
                suffixes[i].rank[0] = rank
            else:
                prev_rank = suffixes[i].rank[0]
                rank += 1
                suffixes[i].rank[0] = rank
            ind[suffixes[i].index] = i
        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 suffixArr
  
# Function to construct and return LCP (Longest Common Prefix)
def kasai(txt, suffixArr):
    n = len(suffixArr)
    lcp = [0]*# To store lcp of all substring pairs
    invSuff = [0]*# To get next substring from suffix array
    for i in range(n):
        invSuff[suffixArr[i]] = i
    k = 0
    for i in range(n):
        if invSuff[i] == n - 1:
            k = 0
            continue
        # Get the next substring from suffix array
        j = suffixArr[invSuff[i] + 1]
        # To increase k while txt[i+k] and txt[j+k] are equal
        while i + k < n and j + k < n and txt[i + k] == txt[j + k]:
            k += 1
        lcp[invSuff[i]] = # lcp for the present substring pair
        # To decrease the value of k for next suffix
        if k > 0:
            k -= 1
    return lcp
  
# Function to find the longest common extension from index - L and index - R
def LCE(lcp, invSuff, n, L, R):
    if L == R:
        return n - L
    low = min(invSuff[L], invSuff[R])
    high = max(invSuff[L], invSuff[R])
    length = lcp[low]
    for i in range(low + 1, high):
        if lcp[i] < length:
            length = lcp[i]
    return length
  
# Function to answer queries of the longest common extension
def LCEQueries(txt, suffixArr, q):
    n = len(txt)
    invSuff = [0]*n
    for i in range(n):
        invSuff[suffixArr[i]] = i
    lcp = kasai(txt, suffixArr)
    for query in q:
        L = query.L
        R = query.R
        print(f"LCE ({L}, {R}) = {LCE(lcp, invSuff, n, L, R)}")
  
def main():
    txt = "abbababba"
    queries = [Query(1, 2), Query(1, 6), Query(0, 5)]
    suffixArr = build_suffix_array(txt)
    LCEQueries(txt, suffixArr, queries)
  
if __name__ == "__main__":
    main()




// Using System namespace for basic I/O operations
using System;
using System.Linq;
  
// Definition of the Query class representing a query range
public class Query
{
    public int L, R;
  
    public Query(int L, int R)
    {
        this.L = L;
        this.R = R;
    }
}
  
// Definition of the Suffix class implementing IComparable
public class Suffix : IComparable<Suffix>
{
    public int index;
    public int[] rank = new int[2];
  
    public Suffix(int index)
    {
        this.index = index;
    }
  
    public int CompareTo(Suffix other)
    {
        return (this.rank[0] == other.rank[0]) ? this.rank[1].CompareTo(other.rank[1]) : this.rank[0].CompareTo(other.rank[0]);
    }
}
  
// Class implementing the Longest Common Extension algorithm
public class LongestCommonExtension
{
    // Function to return the minimum of two integers
    static int minVal(int x, int y) => (x < y) ? x : y;
  
    // Function to return the maximum of two integers
    static int maxVal(int x, int y) => (x > y) ? x : y;
  
    // Function to build the suffix array of a given string
    static int[] buildSuffixArray(string txt)
    {
        int n = txt.Length;
        Suffix[] suffixes = new Suffix[n];
  
        // Initialize suffix array with ranks
        for (int i = 0; i < n; i++)
        {
            suffixes[i] = new Suffix(i);
            suffixes[i].rank[0] = txt[i] - 'a';
            suffixes[i].rank[1] = ((i + 1) < n) ? (txt[i + 1] - 'a') : -1;
        }
  
        // Sort the suffix array using the defined comparison
        Array.Sort(suffixes);
  
        int[] ind = new int[n];
  
        // Iterate for all suffixes and update ranks
        for (int k = 4; k < 2 * n; k *= 2)
        {
            int rank = 0;
            int prev_rank = 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] == prev_rank && 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];
                    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);
        }
  
        int[] suffixArr = new int[n];
        for (int i = 0; i < n; i++)
        {
            suffixArr[i] = suffixes[i].index;
        }
  
        return suffixArr;
    }
  
    // Function to compute the longest common prefix (LCP) array using Kasai's algorithm
    static int[] kasai(string txt, int[] suffixArr)
    {
        int n = suffixArr.Length;
        int[] lcp = new int[n];
        int[] invSuff = new int[n];
  
        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;
    }
  
    // Function to compute the Longest Common Extension (LCE) given queries
    static int LCE(int[] lcp, int[] invSuff, int n, int L, int R)
    {
        if (L == R)
        {
            return n - L;
        }
  
        int low = minVal(invSuff[L], invSuff[R]);
        int high = maxVal(invSuff[L], invSuff[R]);
  
        int length = lcp[low];
  
        for (int i = low + 1; i < high; i++)
        {
            if (lcp[i] < length)
            {
                length = lcp[i];
            }
        }
  
        return length;
    }
  
    // Function to compute LCE for a set of queries
    static void LCEQueries(string txt, int[] suffixArr, Query[] q)
    {
        int n = txt.Length;
        int[] invSuff = new int[n];
  
        for (int i = 0; i < n; i++)
        {
            invSuff[suffixArr[i]] = i;
        }
  
        int[] lcp = kasai(txt, suffixArr);
  
        // Iterate through each query and compute LCE
        foreach (Query query in q)
        {
            int L = query.L;
            int R = query.R;
  
            Console.WriteLine("LCE (" + L + ", " + R + ") = " + LCE(lcp, invSuff, n, L, R));
        }
    }
  
    // Driver Code
    public static void Main(string[] args)
    {
        // Sample input string
        string txt = "abbababba";
        int n = txt.Length;
  
        // Sample queries
        Query[] queries = { new Query(1, 2), new Query(1, 6), new Query(0, 5) };
  
        // Build the suffix array
        int[] suffixArr = buildSuffixArray(txt);
  
        // Compute LCE for the given queries
        LCEQueries(txt, suffixArr, queries);
    }
}




// Definition of the Query class representing a query range
class Query {
    constructor(L, R) {
        this.L = L;
        this.R = R;
    }
}
  
// Definition of the Suffix class implementing Comparable
class Suffix {
    constructor(index) {
        this.index = index;
        this.rank = [0, 0];
    }
  
    compareTo(other) {
        return (this.rank[0] === other.rank[0]) ? this.rank[1] - other.rank[1] : this.rank[0] - other.rank[0];
    }
}
  
// Class implementing the Longest Common Extension algorithm
class LongestCommonExtension {
    // Function to return the minimum of two integers
    static minVal(x, y) {
        return (x < y) ? x : y;
    }
  
    // Function to return the maximum of two integers
    static maxVal(x, y) {
        return (x > y) ? x : y;
    }
  
    // Function to build the suffix array of a given string
    static buildSuffixArray(txt) {
        const n = txt.length;
        const suffixes = Array.from({ length: n }, (_, i) => new Suffix(i));
  
        // Initialize suffix array with ranks
        for (let i = 0; i < n; 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;
        }
  
        // Sort the suffix array using the defined comparison
        suffixes.sort((a, b) => a.compareTo(b));
  
        const ind = new Array(n);
  
        // Iterate for all suffixes and update ranks
        for (let k = 4; k < 2 * n; k *= 2) {
            let rank = 0;
            let prev_rank = 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] === prev_rank && 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];
                    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) => a.compareTo(b));
        }
  
        const suffixArr = suffixes.map(suffix => suffix.index);
        return suffixArr;
    }
  
    // Function to compute the longest common prefix (LCP) array using Kasai's algorithm
    static kasai(txt, suffixArr) {
        const n = suffixArr.length;
        const lcp = new Array(n).fill(0);
        const invSuff = new Array(n);
  
        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 to compute the Longest Common Extension (LCE) given queries
    static LCE(lcp, invSuff, n, L, R) {
        if (L === R) {
            return n - L;
        }
  
        const low = LongestCommonExtension.minVal(invSuff[L], invSuff[R]);
        const high = LongestCommonExtension.maxVal(invSuff[L], invSuff[R]);
  
        let length = lcp[low];
  
        for (let i = low + 1; i < high; i++) {
            if (lcp[i] < length) {
                length = lcp[i];
            }
        }
  
        return length;
    }
  
    // Function to compute LCE for a set of queries
    static LCEQueries(txt, suffixArr, queries) {
        const n = txt.length;
        const invSuff = new Array(n);
  
        for (let i = 0; i < n; i++) {
            invSuff[suffixArr[i]] = i;
        }
  
        const lcp = LongestCommonExtension.kasai(txt, suffixArr);
  
        // Iterate through each query and compute LCE
        for (const query of queries) {
            const L = query.L;
            const R = query.R;
  
            console.log(`LCE (${L}, ${R}) = ${LongestCommonExtension.LCE(lcp, invSuff, n, L, R)}`);
        }
    }
  
    // Driver Code
    static main() {
        // Sample input string
        const txt = "abbababba";
        const n = txt.length;
  
        // Sample queries
        const queries = [new Query(1, 2), new Query(1, 6), new Query(0, 5)];
  
        // Build the suffix array
        const suffixArr = LongestCommonExtension.buildSuffixArray(txt);
  
        // Compute LCE for the given queries
        LongestCommonExtension.LCEQueries(txt, suffixArr, queries);
    }
}
  
// Run the main method
LongestCommonExtension.main();
  
// This code is contributed by shivamgupta0987654321

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










Analysis of Reduction to RMQ method
Time Complexity : 

Hence the overall time complexity is O(N.logN + Q. (|invSuff[R] – invSuff[L]|))
where,
Q = Number of LCE Queries.
N = Length of the input string.
invSuff[] = Inverse suffix array of the input string.
Although this may seems like an inefficient algorithm but this algorithm generally outperforms all other algorithms to answer the LCE queries.
We will give a detail description of the performance of this method in the next set.

Auxiliary Space: We use O(N) auxiliary space to store lcp, suffix and inverse suffix arrays.


Article Tags :