115 lines
3.9 KiB
YAML
115 lines
3.9 KiB
YAML
title: Longest Substring Without Repeating Characters
|
|
slug: longest-substring-without-repeating
|
|
difficulty: medium
|
|
leetcode_id: 3
|
|
leetcode_url: https://leetcode.com/problems/longest-substring-without-repeating-characters/
|
|
categories:
|
|
- strings
|
|
- hash-tables
|
|
patterns:
|
|
- sliding-window
|
|
|
|
description: |
|
|
Given a string `s`, find the length of the longest substring without repeating characters.
|
|
|
|
constraints: |
|
|
- 0 <= s.length <= 5 * 10^4
|
|
- s consists of English letters, digits, symbols and spaces
|
|
|
|
examples:
|
|
- input: 's = "abcabcbb"'
|
|
output: "3"
|
|
explanation: "The answer is 'abc', with length 3."
|
|
- input: 's = "bbbbb"'
|
|
output: "1"
|
|
explanation: "The answer is 'b', with length 1."
|
|
- input: 's = "pwwkew"'
|
|
output: "3"
|
|
explanation: "The answer is 'wke', with length 3."
|
|
|
|
explanation:
|
|
approach: |
|
|
1. Use a sliding window with left and right pointers
|
|
2. Maintain a set of characters in the current window
|
|
3. Expand right pointer, adding characters to the set
|
|
4. When a duplicate is found, shrink from left until duplicate is removed
|
|
5. Track maximum window size throughout
|
|
|
|
intuition: |
|
|
We're looking for the longest contiguous substring where all characters are unique.
|
|
A sliding window naturally represents a substring.
|
|
|
|
When we encounter a duplicate, the current window is invalid. Instead of restarting
|
|
from scratch, we shrink the window from the left until the duplicate is removed.
|
|
This way, we never revisit characters unnecessarily.
|
|
|
|
common_pitfalls:
|
|
- title: Resetting the window incorrectly
|
|
description: |
|
|
When finding a duplicate, don't start over from the next character.
|
|
Instead, shrink from the left until the duplicate is removed.
|
|
wrong_approach: "left = right when duplicate found"
|
|
correct_approach: "Increment left until duplicate is out of window"
|
|
|
|
- title: Not handling empty string
|
|
description: |
|
|
An empty string should return 0. Make sure the algorithm handles this.
|
|
|
|
- title: Off-by-one in length calculation
|
|
description: |
|
|
Window length is right - left + 1, or just track max before removing.
|
|
|
|
key_takeaways:
|
|
- Sliding window is ideal for substring problems with constraints
|
|
- Use a set or map to track elements in current window
|
|
- Shrinking from one end maintains the contiguous property
|
|
- This pattern appears in many "longest/shortest with constraint" problems
|
|
|
|
time_complexity: "O(n)"
|
|
space_complexity: "O(min(m, n))"
|
|
complexity_explanation: |
|
|
Time: Each character is visited at most twice (once by right, once by left).
|
|
Space: The set holds at most min(n, m) characters where m is the charset size.
|
|
|
|
solutions:
|
|
- approach_name: Sliding Window with Set (Optimal)
|
|
is_optimal: true
|
|
code: |
|
|
def length_of_longest_substring(s: str) -> int:
|
|
char_set = set()
|
|
left = 0
|
|
max_length = 0
|
|
|
|
for right in range(len(s)):
|
|
while s[right] in char_set:
|
|
char_set.remove(s[left])
|
|
left += 1
|
|
|
|
char_set.add(s[right])
|
|
max_length = max(max_length, right - left + 1)
|
|
|
|
return max_length
|
|
explanation: |
|
|
Expand window by moving right pointer.
|
|
When duplicate found, shrink from left until window is valid again.
|
|
|
|
- approach_name: Optimized with Hash Map
|
|
is_optimal: true
|
|
code: |
|
|
def length_of_longest_substring(s: str) -> int:
|
|
char_index = {}
|
|
left = 0
|
|
max_length = 0
|
|
|
|
for right, char in enumerate(s):
|
|
if char in char_index and char_index[char] >= left:
|
|
left = char_index[char] + 1
|
|
|
|
char_index[char] = right
|
|
max_length = max(max_length, right - left + 1)
|
|
|
|
return max_length
|
|
explanation: |
|
|
Store the last index of each character.
|
|
Jump left pointer directly past the duplicate instead of shrinking one by one.
|