Longest substring without repeating characters
A window with two edges and a map of where each character was last seen. Rune-aware, so it counts characters, not bytes.
- Write a rune-aware sliding window for the longest unique substring.
- Explain why the j >= start check is needed.
- Predict start and best at each step for a small string.
The problem
Given a string, return the length of the longest substring with no repeated character. "abcabcbb" gives 3 ("abc").
Brute force first
Try every start i. Extend j to the right with a fresh map[rune]bool, and stop at the first repeat. Track the longest.
O(n²) time (O(n³) if you recheck each substring from scratch), O(k) space. The window never throws away work it has already done.
The approach
Keep a window [start, i] with no repeats. Move i forward one character at a time. If that character was last seen inside the window, move start just past it. The window only ever moves right, so this is O(n) time, and O(k) space for k distinct characters.
package mainimport "fmt"// longestUnique returns the length, in characters, of the longest substring// with no repeated character. It works on runes, so "héllo" counts é once.func longestUnique(s string) int { last := make(map[rune]int) // rune -> index of its most recent position best, start := 0, 0 for i, r := range []rune(s) { if j, ok := last[r]; ok && j >= start { start = j + 1 // jump the left edge past the earlier copy } last[r] = i best = max(best, i-start+1) } return best}func main() { fmt.Println(longestUnique("abcabcbb")) // 3 (abc) fmt.Println(longestUnique("bbbbb")) // 1 fmt.Println(longestUnique("pwwkew")) // 3 (wke) fmt.Println(longestUnique("héllo")) // 3 (hél) fmt.Println(longestUnique("")) // 0}Dry run
"abcabcbb", window [start, i]:
| i | char | last seen | start | window | best |
|---|---|---|---|---|---|
| 0 | a | - | 0 | a | 1 |
| 1 | b | - | 0 | ab | 2 |
| 2 | c | - | 0 | abc | 3 |
| 3 | a | 0 (in window) | 1 | bca | 3 |
| 4 | b | 1 (in window) | 2 | cab | 3 |
| 5 | c | 2 (in window) | 3 | abc | 3 |
| 6 | b | 4 (in window) | 5 | cb | 3 |
| 7 | b | 6 (in window) | 7 | b | 3 |
Go-specific gotchas
- `range` over a string gives byte offsets.
for i, r := range sjumpsiby 2 foré. Here we range over[]rune(s)soicounts characters, which makesi-start+1a length in characters. - Check `j >= start`, not just that the rune was seen. A copy from before the window must not move
startbackwards. - Comma-ok again. Index 0 is a real position, so
last[r] == 0cannot mean "not seen". - For ASCII-only input, a
[128]intarray is faster than a map. Say it as an optimisation, after it works.
- Two edges; the right one always moves, the left one only jumps forward.
- Map from character to last index.
- Ranging over a string gives byte offsets; convert to
[]runewhen you need character positions.
longestUnique(s string) int that returns the length, in characters, of the longest substring with no repeated character. Example: "abcabcbb" → 3, "héllo" → 3.Tab indents. Press Esc, then Tab, to leave the editor.
- 1The main task: Find the length of the longest substring with at most two distinct characters. Shrink the window from the left when a third one arrives.
- 2Dry run
"abba". Ati = 3theawas last seen at index 0. What isstart, and why does thej >= startcheck matter here? - 3Find the maximum sum of any
kconsecutive numbers ([2 1 5 1 3 2], k = 3 → 9).