Purpose: Given a string and its suffix array, build the LCP arraylcp[i] = length of the longest common prefix of sa[i] and sa[i-1] — in time.

Algorithm

  1. Build rank[], the inverse of the suffix array: rank[sa[i]] = i.
  2. Walk the suffixes in string order (i = 0 … n-1), not suffix-array order, maintaining a running value .
  3. For suffix at rank : if there is no predecessor, so set and continue.
  4. Otherwise let and extend the match character by character from the current : while s[i+k] == s[j+k], increment .
  5. Store lcp[r] = k, then decrement (but not below 0) before moving to suffix .

Code

vector<int> kasai(const string& s, const vector<int>& sa) {
    int n = s.size();
    vector<int> rank(n), lcp(n, 0);
    for (int i = 0; i < n; i++) rank[sa[i]] = i;
 
    int k = 0;
    for (int i = 0; i < n; i++) {
        if (rank[i] == 0) { k = 0; continue; }
        int j = sa[rank[i] - 1];
        while (i + k < n && j + k < n && s[i + k] == s[j + k]) k++;
        lcp[rank[i]] = k;
        if (k) k--;
    }
    return lcp;
}

Paradigm

Amortized two-pointer. Nothing clever happens per suffix; the magic is entirely in the accounting.

Complexity

  • Time:
  • Space: for rank and lcp

Proof of Correctness

The key lemma is:

Why: suppose suffix shares a prefix of length with its suffix-array predecessor . Then suffix (which is suffix with the first character removed) shares a prefix of length with suffix . Suffix sits somewhere before suffix in sorted order, and the LCP with the immediate predecessor is at least the LCP with any earlier suffix (LCP values along the suffix array are the minimum over the intervening range). Hence lcp[rank[i]] ≥ k - 1.

That lemma is exactly what licenses starting the character comparison loop at instead of . Now count: increases by 1 per successful character comparison and decreases by at most 1 per outer iteration. There are outer iterations, so decreases at most times total; since always, it can increase at most times. Total comparisons . ∎

Variants / Use Cases

  • Number of distinct substrings
  • Longest repeated substring
  • Longest common substring of two strings — concatenate with a separator, take the max lcp between suffixes from different halves
  • LCP of arbitrary suffixes — a sparse table RMQ over the LCP array answers in
  • Suffix array + LCP as a poor man’s suffix tree — most suffix-tree problems have an SA+LCP formulation
  • SA-IS — the linear-time way to get the suffix array Kasai consumes