Longest Substring Without Repeating Characters
Return the length of the longest substring of s with no repeating characters.
Intuition
Keep a window that holds only distinct characters. As the right edge moves forward, if it hits a character already inside the window, jump the left edge just past that character's previous spot. The window never shrinks needlessly.
Two clues put you on a sliding window: the answer is a contiguous stretch, and the constraint is monotone — once a window is invalid, growing it right can't fix it. Then ask whether the left edge should crawl or jump. If you can store where the offender was, jump the left edge past it in O(1) instead of shrinking one character at a time.
Approach
Checking every substring is quadratic
There are O(n²) substrings, and verifying each for distinct characters re-scans constantly. But the substrings overlap enormously — extending a known-good window by one character barely changes anything. That overlap is the cue for a sliding window: maintain one contiguous range and adjust its edges instead of rebuilding from scratch.
Grow the right edge, jump the left edge past repeats
Keep a window [left, right] that contains only distinct characters. Push right forward one character at a time. The only thing that can break the window is encountering a character already inside it — when that happens, we must move left just past that character's previous position so the duplicate falls out of the window. To know where that was, store each character's most recent index in a map.
Guard against stale positions; track the best
When s[right] was last seen at some index, only jump left if that index is ≥ left — otherwise the earlier occurrence is already outside the window and left must not move backward. After adjusting, record last[ch] = right and update best = max(best, right − left + 1). Each index enters and leaves the window once, so it's a single O(n) pass; space is bounded by the character set.
Solution & live demo
Common pitfalls
Moving left backwards on a stale index
if ch in last:
left = last[ch] + 1if ch in last and last[ch] >= left:
left = last[ch] + 1last keeps every character ever seen, including ones already outside the window. On "abba", when the second a arrives left is already 2, but last['a'] is 0 — dragging left back to 1 re-admits the b that was deliberately excluded and reports 3. The >= left test ignores repeats that have already fallen out of the window.
Recording the character after measuring the window
best = max(best, right - left + 1) last[ch] = right
last[ch] = right best = max(best, right - left + 1)
Here it happens to be harmless, but it's fragile ordering: the window is only genuinely valid once the current character has been registered. Update state, then measure — the same discipline that keeps the crawling variant correct.
Edge cases
Each new character forces left to follow right, so the window length stays 1.
The loop never runs; best stays 0.
The last[ch] >= left guard ignores stale occurrences, so left never moves backward.