Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
String Algorithms  ›  Lesson

Suffix Arrays & the Longest Repeat

Suffix Arrays 28 minSorted suffixes put anything alike next to each other; the LCP array measures exactly how alike
You're building a piece ofQuarry — the text index
This piece — longest_repeated_substring(): Sorts every suffix of a document to find its longest repeated chunk without scanning every substring pair.
Scenario Quarry's text index flags the longest chunk of a document that repeats somewhere else in it, to catch copy-pasted boilerplate before it's indexed twice.
Your task
Build longest_repeated_substring(s): the longest substring of s that occurs more than once (overlapping occurrences count), using a suffix array and an LCP array. Return "" if nothing repeats. Example: longest_repeated_substring("banana") -> "ana", longest_repeated_substring("abcabcabc") -> "abcabc", longest_repeated_substring("abcd") -> "".

Suffix Arrays & the Longest Repeat

In short: a suffix array is every suffix of a string, sorted alphabetically — stored as just their starting indices, not copies of the text. Once sorted, two suffixes that share a long prefix sit next to each other, because sorting groups anything alike. The LCP array (longest common prefix) measures exactly how much each neighbouring pair shares. Together they turn "does this substring repeat, and how much" from a scan-everything question into a read-the-neighbours question.

A sorted list of banana's six suffixes stacked vertically, with the shared-letter count written in the gap between each pair, the biggest gap between ana and anana highlighted

Sorting suffixes instead of comparing substrings

"banana" has 6 suffixes: banana, anana, nana, ana, na, a. Sorted alphabetically:

python

The array stores indices, [5, 3, 1, 0, 4, 2], not the suffix strings themselves — slicing s[i:] is only for printing. Building it this way, by sorting Python string slices, is O(n² log n) in the worst case (each comparison can itself cost O(n)), fine for a lesson-sized string; production suffix-array builders use O(n log n) radix-style construction, which is past this lesson's scope.

The LCP array: what sorting buys you

Walk the sorted list and measure how much each suffix shares with the one right before it:

python

The 3 is ana and anana sharing ana — the longest repeated substring in "banana" is sitting in that one gap, found without ever comparing two suffixes that aren't neighbours in the sorted order. Any repeated substring of length k means two suffixes that agree on their first k characters — and sorting has already put every pair that agrees on anything as close together as possible.

watch it move · array

banana's six suffixes, sorted, with each one's LCP against its LEFT neighbour as the label underneath. The 3 between ana and anana is the longest repeat.

a and ana share 1 letter: a. 6 cells. Comparing a and ana.

1 / 5

Quick practice

What does this print?
def suffix_array(s):
    n = len(s)
    return sorted(range(n), key=lambda i: s[i:])

print(suffix_array("abab"))
broken — fix itIndexError

This crashes with an IndexError the moment two suffixes match all the way to the end of the shorter one. Add the missing length guards.

What it costs

operation cost why
naive suffix array (sort string slices) O(n² log n) worst case each comparison can itself take O(n), and there are n log n comparisons
LCP array from a built suffix array O(n²) worst case (naive), O(n) with Kasai's algorithm this lesson uses the naive version; Kasai's is a fast-follow, not covered here
longest repeated substring, once both arrays exist O(n) one scan for the max LCP
the naive "compare every pair of substrings" approach O(n³) or worse no sorting, no shared structure to exploit

🎯 Your turn

Build longest_repeated_substring(s): the longest substring of s that occurs more than once (overlapping occurrences count), using a suffix array and an LCP array. Return "" if nothing repeats.

  • longest_repeated_substring("banana") → "ana"
  • longest_repeated_substring("abcabcabc") → "abcabc"
  • longest_repeated_substring("abcd") → ""

Hint: build the suffix array, build the LCP array from it, then the answer is the suffix at the position of the maximum LCP value, sliced to that LCP's length.

Press ▶ Run, then ✓ Check.

Practice — level up

0/5 solved

Solve each in its own editor — Run to try, Check to grade. Stuck? Reveal a Hint, or the full Solution + walkthrough.

Warm-upSort the suffixes

Write suffix_array(s): the starting index of every suffix of s, sorted alphabetically by the suffix itself.

sortingstring-matching
DrillHow much do neighbours share?

Write lcp_array(s, sa): for a suffix array sa of s, the length of the longest common prefix between each suffix and the one right before it in sa (position 0 is always 0).

string-matching
BuildHow many different substrings are there?

Write count_distinct_substrings(s): the number of DIFFERENT substrings of s (every substring counted once, however many times it repeats). There are n*(n+1)/2 substrings total if none repeated; subtract the LCP array's total to remove the repeats.

string-matchingcombinatorics
BossBinary search the suffix array

Write search_pattern(s, pattern): whether pattern occurs anywhere in s, using binary search over the suffix array instead of scanning s.

binary-searchstring-matching
CapstoneThe longest substring shared by two strings

Write longest_common_substring(a, b): the longest substring that occurs in BOTH a and b. Build one suffix array over a + "\x00" + b (a separator that can't appear in either string), and look for the biggest LCP between two suffixes that start on DIFFERENT sides of the separator.

string-matchingsorting
longest_repeated_substring.py
Call the function you wrote — just like your app's frontend would. Edit the inputs and hit Call.
longest_repeated_substring(s) → strThe longest substring that occurs more than once, or "" if nothing repeats.
try:
returned
Press Run to execute your code.
Press Check to run the tests for this lesson.