Given a string s, find the length of the longest substring without duplicate characters.
Example 1:
Input: s = "abcabcbb"
Output: 3
Explanation: The answer is "abc", with the length of 3. Note that "bca" and "cab" are also correct answers.
Example 2:
Input: s = "bbbbb"
Output: 1
Explanation: The answer is "b", with the length of 1.
Example 3:
Input: s = "pwwkew"
Output: 3
Explanation: The answer is "wke", with the length of 3.
Notice that the answer must be a substring, "pwke" is a subsequence and not a substring.
Given a string s, return the length of the longest substring without repeating characters.
Important:
Substring:
"abc" in "abcabcbb" is valid.Subsequence:
"pwke" from "pwwkew" is subsequence, not substring.You might think:
βTry every possible substring and check if it has duplicates.β
That works.
But how many substrings exist?
If string length = $n$
Number of substrings β $n^2$
If n = 50,000 β $n^2$ = 2.5 billion β too slow.
So brute force is impossible.
We need something $O(n)$.
We donβt need to restart every time.
We can:
This is called:
Imagine two pointers:
l (left pointer)
r (right pointer)
They form a window:
s[l ... r]
This window always contains unique characters only.
We grow r step by step.
If a duplicate appears:
l forwardl = 0best = 0last = {} (stores last index of each character)r:
ch = s[r]ch appeared before AND its last index β₯ l:
β Move l to last[ch] + 1last[ch] = rbest = max(best, r - l + 1)last[ch] >= lThis is CRITICAL.
Consider:
s = "abba"
Walk through it:
window = βaβ best = 1
window = βabβ best = 2
duplicate found! last[βbβ] = 1
So move l to 1 + 1 = 2
window = βbβ best still 2
last[βaβ] = 0
BUT 0 < l (l = 2)
That means this βaβ is outside the current window.
So we DO NOT move l.
This is why we check:
if last[ch] >= l
We only react if duplicate is inside current window.
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
last = {} # char -> last index seen
l = 0 # left pointer of window
best = 0 # maximum length found
for r, ch in enumerate(s):
# If character seen before and inside current window
if ch in last and last[ch] >= l:
l = last[ch] + 1 # jump left pointer
last[ch] = r
best = max(best, r - l + 1)
return best
last = {}Stores the last position of each character.
Example:
{'a': 0, 'b': 2}
l = 0Left boundary of sliding window.
for r, ch in enumerate(s):Move right pointer step by step.
if ch in last and last[ch] >= l:Two conditions:
l = last[ch] + 1Jump left pointer directly after duplicate.
We do NOT move one by one. We βteleportβ it.
This is optimization.
last[ch] = rUpdate latest position.
best = max(best, r - l + 1)Current window length = r - l + 1
l=0
best=0
r=0 β a window=βaβ best=1
r=1 β b window=βabβ best=2
r=2 β c window=βabcβ best=3
r=3 β a duplicate! move l = 0+1 =1 window=βbcaβ best=3
r=4 β b duplicate! move l=1+1=2 window=βcabβ best=3
r=5 β c duplicate! move l=2+1=3 window=βabcβ best=3
r=6 β b duplicate! move l=4+1=5 window=βcbβ best=3
r=7 β b duplicate! move l=6+1=7 window=βbβ best=3
Final answer = 3
Window never grows beyond 1.
Answer = 1
r=0 β p β best=1 r=1 β w β best=2 r=2 β w duplicate β l=2 β best=2 r=3 β k β best=2 r=4 β e β best=3 r=5 β w duplicate β adjust β best=3
Answer = 3
Each character is:
Neither pointer moves backward.
Total operations β 2n
Time complexity: O(n) Space complexity: O(min(n, charset))
Too slow.
last[ch] >= lCauses wrong shrinking.
You can solve with set, but map is cleaner and faster.
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
seen = set()
l = 0
best = 0
for r in range(len(s)):
while s[r] in seen:
seen.remove(s[l])
l += 1
seen.add(s[r])
best = max(best, r - l + 1)
return best
This shrinks step-by-step.
Still O(n), but less elegant.
This problem teaches:
for r in range(n):
add s[r]
while window invalid:
remove s[l]
l += 1
update answer
You will see this in:
Say this:
βBrute force is O(nΒ²). Instead, I maintain a sliding window with unique characters. I use a hash map to track last seen index. If a duplicate appears inside the window, I move left pointer to avoid duplicates. This guarantees O(n) time.β
Perfect explanation.
if ch in last and last[ch] >= l:
l = last[ch] + 1
That line is the heart.