Skip to content

Coding patternsBlind 75

Longest Substring Without Repeating Characters

Keep a window of distinct characters, grow it on the right and shrink it on the left whenever a repeat comes in. Solved two ways, brute force in O(n²) and a sliding window in O(n), with a step-through, code in five languages and the Big O worked out from the code.

You are given a string text. Find the longest substring in which no character appears twice, and return its length. A substring is a run of characters that sit next to each other in text.

Example 1

Input: text = "abcbdab"

Output: 4

“cbda” has four different characters. Every longer substring repeats a character.

text
a0b1c2b3d4a5b6

Example 2

Input: text = "zzzz"

Output: 1

Any two characters next to each other are the same, so the best is a single “z”.

Example 3

Input: text = ""

Output: 0

The empty string has no characters at all.

  • 0 ≤ n ≤ 50,000, where n is the length of text
  • text holds English letters, digits, symbols and spaces. Below, m is the number of different characters that can appear (under 100 here).
Substring or subsequence?
Substring. The characters must sit next to each other. We cannot skip any.
Do uppercase and lowercase count as different?
Yes. "a" and "A" are different characters.
Do spaces count?
Yes. A space is a character like any other, so " " has an answer of 1.
Do we return the substring or its length?
Only the length.

We want the longest contiguous run that obeys a rule (“no repeats”). And if a run breaks the rule, every longer run around it breaks it too. That is the sliding window pattern: grow the window on the right, and shrink it from the left only as much as needed to make it valid again.

This problem has two approaches. Checking every substring separately would be O(n³), which is just a slower brute force.

Sliding window O(n)

Keep a window, from left to right, that always holds distinct characters. A set holds what is inside the window.

Move right one step at a time. If the new character is already in the window, shrink the window from the left, one character at a time, until that character has left. Then add the new character and measure the window.

We never move left backward. Here is why that is safe: any window that starts further left and ends at right would contain the repeat, so it can never be valid.

The invariant: after each step, the window is the longest substring with no repeats that ends at right.

text
a0b1c2b3d4a5b6
seen
empty
best
0
  1. seen = set()
  2. left = best = 0
  3. for right in range(len(text)):
  4. while text[right] in seen:
  5. seen.remove(text[left])
  6. left += 1
  7. seen.add(text[right])
  8. best = max(best, right - left + 1)
  9. return best

The window from left to right always holds distinct characters. A set holds what is inside it.

1 / 13
Solution.javasolution.pySolution.cssolution.gosolution.ts
class Solution {
// Sliding window: the window left..right always holds distinct characters.
// Each character enters once and leaves at most once, so O(n) time.
public int lengthOfLongestSubstring(String text) {
Set<Character> seen = new HashSet<>();
int left = 0, best = 0;
for (int right = 0; right < text.length(); right++) {
// Shrink from the left until the new character is no longer inside.
while (seen.contains(text.charAt(right))) {
seen.remove(text.charAt(left));
left++;
}
seen.add(text.charAt(right));
best = Math.max(best, right - left + 1);
}
return best;
}
}
Time O(n)Space O(m)Here is how we get these from the code, in four steps.
  1. 1 Find the work that repeats

    Two kinds of work repeat. Each run of the for loop adds one character. Each run of the inner while loop removes one character. Both are 1 step. The while sits inside the for, so the question is how often it really runs in total.

    1. seen = set()
    2. left = best = 0
    3. for right in range(len(text)):
    4. while text[right] in seen:
    5. seen.remove(text[left])
    6. left += 1
    7. seen.add(text[right])
    8. best = max(best, right - left + 1)
    9. return best
  2. 2 Count the runs on Example 1

    Example 1 has 7 characters. right moves 7 times, adding each character once. left moves 4 times in total: twice at the first repeated “b”, and twice at the last “b”.

    7 + 4 = 11 runs.

    pointerright (adds)left (removes)
    moves on Example 174
    total
    7 + 4 = 11
  3. 3 Write the count for any n

    right moves n times. left only moves forward and never passes right, so across the whole run it moves at most n times:

    n (adds) + at most n (removes) ≤ 2n

    Check it with Example 1: 11 runs, below 2 × 7 = 14.

  4. 4 Keep only the part that grows fastest

    Drop the 2. Two passes of pointers do not change how the work grows. What is left is n, even with a loop inside a loop.

    So the time is O(n).

  5. S Space: count the extra memory the same way

    The set holds the window’s characters, and the window has no repeats. So it holds at most m characters. The space is O(m).

    seen (up to m characters)leftrightbest

2,000 random texts of 0 to 10 characters from a, b and a space, so repeats are close together and the empty text comes up too. The brute force and the sliding window gave the same answer every time.

Largest inputsResult
50,000 characters (the most allowed): all 95 printable characters, over and over, so every new character pushes one out. Sliding window.0.01 s, and about 0.01 MB of extra memory: the set never holds more than 95 characters, which is the O(m) space.
50,000 characters, same text. Brute force.0.5 s, about 70 times slower. It runs at full size only because each start stops after 96 steps here.