Problem
Given a string, find the length of the longest contiguous block of characters where every character appears exactly once. You need to find the length, not the substring itself.
- Input:
s = "abcabcbb" - Output:
3 - Explanation:
"abc"is the longest run without any character appearing twice
A tricky case: "bbbbb" returns 1 because every extension immediately creates a repeat.
Intuition
The problem asks how far right you can stretch a window before a character appears twice. Instead of restarting from scratch when you hit a duplicate, you can slide the left boundary past the earlier copy โ the characters between are still all unique and can anchor the next stretch. Remembering where you last saw each character (not just whether you saw it) lets you jump the left boundary in one step rather than walking it forward character by character.
Approach 1 โ Brute Force
For each starting position, expand right using a set until a duplicate is found. The maximum window seen across all starting positions is the answer.
- For each index
startfrom 0 to end, initialize an empty set andend = start. - While
end < len(s)ands[end]is not in the set, adds[end]to the set and incrementend. - Record
max_length = max(max_length, end - start). - Return
max_length.
1def length_of_longest_substring(s: str) -> int:
2 max_length = 0
3 for start in range(len(s)):
4 seen = set()
5 end = start
6 while end < len(s) and s[end] not in seen:
7 seen.add(s[end])
8 end += 1
9 max_length = max(max_length, end - start)
10 return max_lengthTime: O(nยฒ) โ in the worst case (all identical characters) the outer loop runs n times and the inner loop processes one character each time, but averaged across distinct characters the inner loop does O(n) total work per outer step.
Space: O(min(n, m)) โ the set holds at most the number of distinct characters in the alphabet m, or n if n is smaller.
Approach 2 โ Sliding Window with Hash Map
Maintain a window [left, right]. Store each character's most recent index. When a duplicate is found, jump left directly past the previous occurrence โ skipping the slow one-by-one shrink.
- Initialize
last_seenmap,left = 0,max_length = 0. - For each
right, look up whether the current character is inlast_seenwith index>= left(inside the current window). - If so, jump
left = last_seen[char] + 1to exclude the earlier copy. - Update
last_seen[char] = right. - Update
max_length = max(max_length, right - left + 1). - Return
max_length.
1def length_of_longest_substring(s: str) -> int:
2 last_seen = {} # char -> index of most recent occurrence
3 left = 0
4 max_length = 0
5
6 for right, char in enumerate(s):
7 if char in last_seen and last_seen[char] >= left:
8 # jump past the earlier copy; the >= left guard prevents
9 # moving left backwards when a stale entry is outside the window
10 left = last_seen[char] + 1
11 last_seen[char] = right
12 max_length = max(max_length, right - left + 1)
13
14 return max_lengthTime: O(n) โ each character is visited exactly once by the right pointer; left only moves forward, never back.
Space: O(min(n, m)) โ the map holds at most one entry per distinct character, bounded by the alphabet size m.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Brute Force | O(nยฒ) | O(min(n, m)) | Understanding the problem; tiny inputs where clarity beats speed |
| Sliding Window with Hash Map | O(n) | O(min(n, m)) | Always prefer this โ same space, linear time |
Common Mistakes
- Forgetting the
>= leftguard on the map lookup โlast_seenis never cleared, so a character encountered before the current window still has an old entry; without the guard,leftjumps backwards into territory already confirmed unique. - Using
left = last_seen[char]instead oflast_seen[char] + 1โ this keeps the duplicate character inside the window, making the window invalid immediately. - Computing window size as
right - leftinstead ofright - left + 1โ the window is inclusive on both ends; skipping+1consistently under-counts by one. - Returning the final window size instead of
max_lengthโ the largest window is not necessarily the last one; you must track the running maximum. - Using a set and shrinking left one step at a time โ correct but O(n) per shrink in the worst case without the jump trick; the hash map avoids this while using the same space.
Related Problems
minimum-window-substringโ sliding window where you must contain a target character set rather than exclude repeatslongest-repeating-character-replacementโ sliding window that allows at most k replacements, extending the "valid window" conditionpermutation-in-stringโ fixed-size sliding window checking whether a window is an anagramfind-all-anagrams-in-a-stringโ same fixed-size anagram window, but collect all starting positionsmax-consecutive-ones-iiiโ sliding window with a budget of k "exceptions," the same left-jump pattern under a different validity rule