AlgoMaster Logo

Longest Substring Without Repeating Characters

mediumFrequency7 min readUpdated June 23, 2026

Understanding the Problem

We need to find the longest contiguous run of characters in s where no character appears more than once. A substring requires the characters to be adjacent in the original string. A subsequence like "pwke" from "pwwkew" does not count because the characters are not contiguous.

The challenge is tracking which characters are in the current window and detecting when a duplicate enters. Checking every possible substring works, but it repeats effort. When a duplicate appears, the prefix we already validated is still valid, so instead of restarting we can shrink the window from the left until the duplicate is gone.

Key Constraints:

  • 0 <= s.length <= 5 * 10^4 → With n up to 50,000, O(n^2) might pass but will be tight. O(n) is what we should aim for.
  • The string can contain letters, digits, symbols, and spaces. That's the full ASCII range (up to 128 unique characters), not just lowercase letters. This affects what data structures we choose for tracking characters.
  • The string can be empty (length = 0), so we need to handle that edge case.

Approach 1: Brute Force

Intuition

Check every possible substring and see whether it has all unique characters. For each starting index i, try every ending index j >= i and verify that the substring s[i..j] contains no duplicates. Track the longest valid substring found.

To check whether a substring has all unique characters, we can use a set. Add each character one by one. If we ever try to add a character that's already in the set, that substring has a duplicate.

Algorithm

  1. Initialize maxLen = 0
  2. For each starting index i from 0 to n-1:
    • Create a new set for this starting position
    • For each ending index j from i to n-1:
      • If s[j] is already in the set, break (any longer substring starting at i will also contain this duplicate)
      • Add s[j] to the set
      • Update maxLen = max(maxLen, j - i + 1)
  3. Return maxLen

Example Walkthrough

1i=0, j=0: 'p' not in set, set={p}, maxLen=1
0
j
p
i
1
w
2
w
3
k
4
e
5
w
1/8

Code

The bottleneck is that when a duplicate appears, this approach discards the whole window and restarts from the next index, even though most of those characters are still valid. The next approach keeps a single window and slides its left boundary forward to drop the duplicate instead of restarting.

Approach 2: Sliding Window + Hash Set

Intuition

Maintain a window [left, right] that always contains unique characters. Expand it by moving right forward. When s[right] is already in the window, move left forward and remove characters from the set until the duplicate is gone, then add s[right].

A hash set holds the characters currently in the window, so the duplicate check is O(1).

Algorithm

  1. Initialize left = 0, maxLen = 0, and an empty hash set
  2. For each right from 0 to n-1:
    • While s[right] is already in the set:
      • Remove s[left] from the set
      • Increment left
    • Add s[right] to the set
    • Update maxLen = max(maxLen, right - left + 1)
  3. Return maxLen

Example Walkthrough

1right=0: 'a' not in set, set={a}, maxLen=1
0
right
a
left
1
b
2
c
3
a
4
b
5
c
6
b
7
b
1/8

Code

This approach is O(n), but it still slides left forward one position at a time on a duplicate. The next approach records where each character last appeared, so left can jump directly past the previous occurrence in a single step.

Approach 3: Sliding Window + Hash Map (Optimal)

Intuition

Replace the set with a hash map that stores the most recent index of each character. On a duplicate, move the left pointer directly past the previous occurrence instead of sliding it one step at a time.

When s[right] already exists in the map at index prevIndex, set left = max(left, prevIndex + 1). The max matters because left may already be past prevIndex from an earlier jump. Without it, left could move backward and re-include a character that was already removed from the window. Concretely, in "abba": after processing the second b, left is at index 2; reaching the final a finds its previous index 0, and max(2, 0 + 1) keeps left at 2 rather than rewinding to 1.

Algorithm

  1. Initialize left = 0, maxLen = 0, and an empty hash map (character to index)
  2. For each right from 0 to n-1:
    • If s[right] exists in the map and its stored index is >= left:
      • Move left to map[s[right]] + 1 (jump past the previous occurrence)
    • Update the map: map[s[right]] = right
    • Update maxLen = max(maxLen, right - left + 1)
  3. Return maxLen

Example Walkthrough

1right=0: 'a' not in map, map={a:0}, maxLen=1
0
right
a
left
1
b
2
c
3
a
4
b
5
c
6
b
7
b
1/9

Code