2Brute2Best
🔥0

Longest Common Substring

Medium·GeeksforGeeksBasic Anchor
GeeksforGeeks
30-Second Mental ModelDP — Strings, Common substring (run = diagonal + 1, reset on mismatch)
Complexity:O(N · M · min(N, M))→O(N · M)
Key Insight

The table only reads the row above it, so one row is enough. Overwrite that row in place. Go j from high to low, so that row[j − 1] still holds the row above when we read it. On a match, row[j] = row[j − 1] + 1. On a mismatch, row[j] = 0. The answer is the largest value ever written.

Core Validity Invariant

A common run must be contiguous, so on a mismatch the run restarts at 0. Then dp[i][j] is the length of the run that ends exactly at these two letters.

01

Problem

You get two strings, s1 and s2. Find the length of the longest substring that appears in both strings.

A substring is a run of characters that sit next to each other. A gap is not allowed. Matching is case-sensitive, so Y and y are different letters.

Return the length as a number.

Input

Two strings s1 and s2.

Output

A single number: the length of the longest common substring. It is 0 when the two strings share no letter.

Example 1
Input: s1 = "ABCDGH", s2 = "ACDGHR"
Output: 4

The longest common substring is "CDGH", which has length 4.

Example 2
Input: s1 = "abc", s2 = "acb"
Output: 1

Only single letters are shared as a run: "a", "b" and "c" each have length 1. The pair "ab" is in s1, but it is not a run in s2.

Example 3
Input: s1 = "YZ", s2 = "yz"
Output: 0

Matching is case-sensitive: Y is not y and Z is not z, so nothing is shared.

Constraints
  • • 1 ≤ s1.length, s2.length ≤ 10^3
  • • s1 and s2 contain only English letters.
  • • Matching is case-sensitive.
02

First, think yourself

Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.

  1. 1.

    A subsequence can skip letters, but a substring cannot. Which one does this problem ask for?

  2. 2.

    If the last letters of two prefixes match, how long is the common run that ends there?

  3. 3.

    What should happen to the run when two letters differ? Can the run jump over that gap?

  4. 4.

    Can the longest run end at any pair of positions, or only at the very end of both strings?

  5. 5.

    Do I need the whole table, or only the row just above the one I am filling?

03

Brute force approach

04

Better approach

05

Optimized approach

06

Why does this algorithm work?

07

Dry run

08

All approaches compared

ApproachIdeaTimeSpace
Brute ForceTry every pair of start positions and extend the run while the letters matchO(N · M · min(N, M))O(1)
BetterFill an (N + 1) × (M + 1) table; a match extends the diagonal cell by oneO(N · M)O(N · M)
OptimalKeep one row; on a match row[j] = row[j − 1] + 1, on a mismatch 0; read j from high to lowO(N · M)O(M)
09

Why does this belong to this pattern?

Clues in the problem statement
  • “substring”A run of letters that sit next to each other. A gap means the run starts again from 0.
  • “appears in both strings”The run must match in s1 and in s2 at the same time, so we compare pairs of positions, one from each string.
  • “longest length”The answer is the largest run that ends at any pair, not the value at the last pair.
Two sequences+Match extends the diagonal+Mismatch resets to 0+Maximum over all cells→DP — Strings, Common substring (run = diagonal + 1, reset on mismatch)

This is the anchor of the Common substring family. The rule that changes everything is the reset: on a mismatch the run goes to 0, not to the best value of the cell above or to the left. That reset is what separates it from the subsequence problem, LCS, which carries the best value across a gap. Maximum Length of Repeated Subarray applies the same rule to integer arrays.

10

Don't make this mistake

Pattern confusion: using the LCS rule on a mismatch
✗ dp[i][j] = s1[i-1] == s2[j-1] ? dp[i-1][j-1] + 1 : max(dp[i-1][j], dp[i][j-1]);
✓ dp[i][j] = s1[i-1] == s2[j-1] ? dp[i-1][j-1] + 1 : 0;

The max rule computes the longest common subsequence, which may skip letters. On the dry run (ABCDGH and ACDGHR) this rule gives 5, the length of the subsequence ACDGH with gaps. The longest common substring is 4 (CDGH), because a substring cannot skip a letter.

Returning the last cell instead of the best cell
✗ return dp[n][m];
✓ return best;

The longest run can end anywhere. On ABCDGH and ACDGHR the last pair is H and R, which differ, so dp[6][6] = 0. The wrong code returns 0 instead of 4.

Going low to high in the one-row version
✗ for (int j = 1; j <= m; j++) { if (s1[i-1] == s2[j-1]) row[j] = row[j-1] + 1; else row[j] = 0; }
✓ for (int j = m; j >= 1; j--) { ... }

Going low to high, row[j − 1] has already been overwritten with this row's value, so the run grows through the wrong letters. On the dry run the answer becomes 1 instead of 4.

11

Want to implement this?

Write it yourself on GeeksforGeeks, then come back and compare your code with the solutions above.

Open coding workspace
How did it go?
Beyond The LeetCode Problem

How this concept connects to System Design & C++ Systems

Bridge DSA with production engineering

Top engineering interviews test whether you understand how algorithms like DP — Strings translate into production architectures and low-level memory mechanics.