CMD Guide
HomeDSADynamic Programming

Longest Repeating Subsequence

Longest Repeating Subsequence (LRS) asks: starting from a string s, align it against itself and find the longest chain of matched character positions where every matched pair (i, j) satisfies s[i] = s[j] and i ≠ j. It is computed as LCS(s, s) with one extra rule bolted onto the matching step: a match is only accepted when the two indices being compared differ, so a character is never matched to itself in the same step.

Important subtlety, not a footnote: the i ≠ j guard only forbids a single matched pair from using the same index twice within that pair. It does not guarantee that the two resulting "copies" of the answer are built from two globally disjoint sets of indices. A single index can legally appear in two different pairs across the chain — once as the earlier index of one pair, once as the later index of the next pair. When that happens, the DP's number is larger than the true "both copies use completely disjoint indices" answer. Concretely, for s = "ababa" this DP reports 3, but the true index-disjoint LRS is 2 (see the worked example below). Treat the classic DP as computing the longest chain of pairwise-differing matches, not literally "two disjoint occurrences" — many summaries of this problem use the latter phrase loosely, and it is only accurate when the matched chain happens not to reuse an index.

Recognize the pattern

Brute force → optimal

Brute force: generate all 2n subsequences of the string, and for each check whether it can be found twice at disjoint index sets (itself an expensive search). This is exponential — infeasible beyond n≈20.

Optimal: treat it as LCS(s, s) where index i is compared against index j, but the recurrence only accepts a match when i ≠ j (otherwise every character trivially "matches itself" and the answer would degenerate to n). This is a direct, one-line modification of the classic LCS DP, so it inherits LCS's polynomial-time structure — but also inherits the reuse subtlety described above, since nothing in the recurrence tracks which indices have already been "spent".

Recurrence

Let s have length n, 1-indexed, and let dp[i][j] = length of the longest pairwise-differing matched chain using s[1..i] and s[1..j].

dp[i][j] =
  dp[i-1][j-1] + 1        if s[i] == s[j] and i != j
  max(dp[i-1][j], dp[i][j-1])   otherwise

base case: dp[0][*] = dp[*][0] = 0
answer: dp[n][n]

Complexity, derived

The DP table has (n+1)×(n+1) cells. Each cell does O(1) work (one comparison, one addition, one max). So time = O(n2) — n2 cells × O(1) each, no hidden multiplicative factor. Space for the table is O(n2); since each row only depends on the row above, this can be compressed to O(n) with two rolling rows if only the length is needed (reconstructing the actual chain still needs the full table or extra bookkeeping).

Worked example — and the disjointness trap

s = "ababa" (n = 5): a(1) b(2) a(3) b(4) a(5). Full table, dp[i][j]:

0a(1)b(2)a(3)b(4)a(5)
0000000
a(1)000111
b(2)000122
a(3)011123
b(4)012223
a(5)012333

Every cell here follows the recurrence exactly as written — no cell needs correcting. dp[5][5] = 3. Backtracking from (5,5) gives the matched chain (1,3), (2,4), (3,5) — exactly three matched pairs, each with i ≠ j, so the DP is not misapplied.

But look at index 3: it is the later index in pair (1,3) and the earlier index in pair (3,5). Reading first-coordinates {1,2,3} spells "aba"; reading second-coordinates {3,4,5} also spells "aba" — but both readings pass through index 3, so they are not disjoint. There is no way to pick 3+3 = 6 distinct indices out of a 5-character string, so a genuinely index-disjoint repeated subsequence of length 3 is impossible here. The true disjoint-index LRS of "ababa" is 2: for example "ab" at indices {1,2} and, separately, "ab" at indices {3,4}. The classic DP overcounts by one on this input precisely because its chain reused index 3.

Pitfalls

When to use / when not — trade-offs

Use the LCS(s,s)-with-guard DP when your problem's definition of "repeating subsequence" is genuinely "a chain of matched positions with i ≠ j at every step" — that is the classic interview/GFG-style framing, and the DP answers it exactly in O(n2) time/space for n up to a few thousand. Do not use it unmodified if the problem insists on two literally disjoint sets of indices for the two occurrences — as shown above, the plain DP can overcount on inputs like "ababa"; that stricter version needs additional bookkeeping (e.g. tracking which indices are consumed) and is a harder combinatorial problem, not a one-line tweak. For very large n, or when you only need duplicated substrings (contiguous), prefer a suffix array / suffix automaton approach (O(n log n)) instead — a different problem shape. If you only need to check existence of any repeated character, a frequency count suffices and the DP is overkill.

Java

public int longestRepeatingSubsequence(String s) {
    int n = s.length();
    int[][] dp = new int[n + 1][n + 1];
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (s.charAt(i - 1) == s.charAt(j - 1) && i != j) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[n][n]; // length of the pairwise-differing chain, not guaranteed index-disjoint
}

Takeaways

Recall: On s = "ababa", why does the matched chain (1,3), (2,4), (3,5) reuse index 3, and what does that imply about calling this DP's result "two disjoint occurrences"?


Pattern derived from the classic Longest Common Subsequence DP (Bellman-style tabulation), adapted with a non-self-match constraint. This is a standard interview variant (e.g., GeeksforGeeks, InterviewBit DP series); those sources typically describe it as finding a "repeating subsequence", and the i ≠ j guard is universal — the claim that it yields two globally disjoint index sets is a common but imprecise gloss, corrected in the worked example above.

🤖 Don't fully get this? Learn it with Claude

Stuck on Longest Repeating Subsequence? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Longest Repeating Subsequence** (DSA) and want to truly understand it. Explain Longest Repeating Subsequence from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Longest Repeating Subsequence** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Longest Repeating Subsequence** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Longest Repeating Subsequence** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes