3. Longest Substring Without Repeating Characters π‘ Medium
Hey Dev.to fam! π Today, we're diving into a LeetCode classic thatβs a fantastic introduction to a super common and powerful algorithm pattern: the Sliding Window. This problem, "Longest Substring Without Repeating Characters," is a must-know for anyone looking to sharpen their algorithmic skills. It might seem tricky at first, but once you grasp the core idea, it opens doors to solving many similar problems!
2. Problem Explanation
Imagine you have a string of characters, like "abcabcbb". Your goal is to find the longest possible piece of that string (a substring) where every character in that piece is unique. No repeats allowed!
Let's break down the examples:
-
Example 1:
s = "abcabcbb"- "abc" is a substring with no repeating characters. Its length is 3.
- "bca" and "cab" are also valid substrings of length 3.
- Can we do better? "abca" has 'a' repeated. "abcab" has 'a' and 'b' repeated.
- So, the longest is 3.
-
Example 2:
s = "bbbbb"- Any substring longer than "b" will have repeating 'b's.
- The longest substring without repeats is "b", with a length of 1.
-
Example 3:
s = "pwwkew"- "pw" -> length 2
- "pww" -> 'w' repeats. Not valid.
- "wke" -> length 3.
- "kew" -> length 3.
- "pwke" is NOT a substring, it's a subsequence (characters not contiguous). We're strictly looking for substrings.
- The longest is 3 (e.g., "wke").
Constraints to keep in mind:
- The string
scan be empty (0length) up to5 * 10^4characters long. - It can contain English letters, digits, symbols, and spaces. This means we're dealing with a diverse set of characters, but their total possible count (e.g., ASCII range) is manageable.
3. Intuition
How would you try to solve this manually? You'd probably start checking substrings:
"a" (len 1)
"ab" (len 2)
"abc" (len 3)
"abca" - oh no, 'a' repeats!
So, "abc" was the longest for that segment. What now? Do you start fresh from "b"?
"b" (len 1)
"bc" (len 2)
"bca" (len 3)
"bcab" - 'b' repeats!
This "try, fail, reset" approach hints at something: when we hit a repeat, we don't necessarily need to throw away everything we've built. We just need to remove the problematic part.
This "expanding a window and shrinking it when things go wrong" is the core idea behind the Sliding Window technique. We'll maintain a "window" of characters and try to expand it as much as possible. If we encounter a duplicate, we'll shrink the window from the other end until the duplicate is gone.
To efficiently check for duplicates within our current window, a HashSet (or unordered_set in C++) is perfect because it allows for lightning-fast (average O(1) time) checks and removals.
4. Approach
We'll use two pointers, left and right, to define our "sliding window" s[left...right].
We'll also use an unordered_set<char> (let's call it charSet) to keep track of unique characters currently within our window.
Here's the step-by-step approach:
-
Initialize:
-
left = 0: This pointer marks the beginning of our current substring. -
maxLength = 0: This will store the maximum length found so far. -
charSet = {}: An empty set to store characters in the current window.
-
-
Iterate with
rightpointer:- Loop
rightfrom0tos.length() - 1. Therightpointer expands our window to the right.
- Loop
-
Handle Duplicates (Shrink the Window):
- Inside the loop, before adding
s[right]to ourcharSet, we check:while (charSet.find(s[right]) != charSet.end())- This
whileloop runs only ifs[right]is ALREADY present in ourcharSet. This meanss[right]is a duplicate within our current windows[left...right-1]. - To remove the duplicate, we need to shrink the window from the
left. We do this by:- Removing
s[left]fromcharSet. - Incrementing
left(left++).
- Removing
- This
whileloop continues untils[right]is no longer a duplicate in the newly shrunk window.
- This
- Inside the loop, before adding
-
Add New Character (Expand the Window):
- Once the
whileloop finishes (meanings[right]is now unique in the windows[left...right-1]), we can safely adds[right]tocharSet:charSet.insert(s[right]).
- Once the
-
Update Maximum Length:
- After
s[right]is added, our current valid substring without repeating characters iss[left...right]. - Its length is
right - left + 1. - Update
maxLength:maxLength = max(maxLength, right - left + 1).
- After
Return
maxLength: After therightpointer has traversed the entire string,maxLengthwill hold the length of the longest substring without repeating characters.
Let's quickly trace s = "abcabcbb":
right |
s[right] |
charSet (before add) |
while loop action |
charSet (after add) |
left |
curr_len (r-l+1) |
maxLength |
Current Window (for clarity) |
|---|---|---|---|---|---|---|---|---|
| 0 | 'a' | {} | - | {'a'} | 0 | 1 | 1 | "a" |
| 1 | 'b' | {'a'} | - | {'a', 'b'} | 0 | 2 | 2 | "ab" |
| 2 | 'c' | {'a', 'b'} | - | {'a', 'b', 'c'} | 0 | 3 | 3 | "abc" |
| 3 | 'a' | {'a', 'b', 'c'} |
erase('a'), l=1
|
{'b', 'c', 'a'} | 1 | 3 | 3 | "bca" |
| 4 | 'b' | {'b', 'c', 'a'} |
erase('b'), l=2
|
{'c', 'a', 'b'} | 2 | 3 | 3 | "cab" |
| 5 | 'c' | {'c', 'a', 'b'} |
erase('c'), l=3
|
{'a', 'b', 'c'} | 3 | 3 | 3 | "abc" |
| 6 | 'b' | {'a', 'b', 'c'} |
erase('a'), l=4
|
|||||
erase('b'), l=5
|
{'c', 'b'} | 5 | 2 | 3 | "cb" | |||
| 7 | 'b' | {'c', 'b'} |
erase('c'), l=6
|
|||||
erase('b'), l=7
|
{'b'} | 7 | 1 | 3 | "b" |
Final maxLength is 3. Perfect!
5. Code
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int left = 0;
int maxLength = 0;
// An unordered_set is perfect for quickly checking for duplicates
// and storing unique characters in our current window.
unordered_set<char> charSet;
// The 'right' pointer expands our window
for (int right = 0; right < s.length(); right++) {
// If the character at 'right' is already in our set,
// it means we have a duplicate within our current window.
// We need to shrink the window from the 'left' until the duplicate is removed.
while (charSet.find(s[right]) != charSet.end()) {
charSet.erase(s[left]); // Remove the character at 'left'
left++; // Move 'left' pointer forward
}
// Once the character at 'right' is unique in the window,
// add it to our set.
charSet.insert(s[right]);
// Update the maximum length found so far.
// Current window length is (right - left + 1).
maxLength = max(maxLength, right - left + 1);
}
return maxLength;
}
};
6. Time & Space Complexity Analysis
Time Complexity: O(N)
- The
rightpointer iterates through the stringsexactly once, performingNincrements. - The
leftpointer also moves forward at mostNtimes in total over the entire execution. Each character is added to thecharSetonce (byright) and removed from thecharSetat most once (byleft). -
unordered_setoperations (insert,find,erase) take average O(1) time. In the worst case (due to hash collisions), they can degrade to O(N), but for character sets (like ASCII), collisions are rare enough that the average case holds true. - Since both pointers traverse the string at most once, and set operations are constant time on average, the total time complexity is O(N), where
Nis the length of the strings.
Space Complexity: O(min(N, M)) or O(1)
- The
charSetstores unique characters present in the current sliding window. - In the worst case, all characters in
smight be unique, so the set could potentially store up toNcharacters. - However, the problem constraints state that
sconsists of English letters, digits, symbols, and spaces. This means the total number of possible unique characters (let's call itM) is limited (e.g., ASCII has 128 or 256 characters). - Therefore, the maximum size of
charSetis bounded bymin(N, M). SinceM(e.g., 128 for ASCII) is a constant, we often simplify the space complexity to O(1) in competitive programming contexts, implying constant extra space relative to the input sizeN.
7. Key Takeaways
- Sliding Window Pattern: This problem is a quintessential example of the "Sliding Window" technique. It's incredibly useful for problems involving subarrays or substrings where you need to find something (max, min, count) within a contiguous range.
- Two Pointers (
left,right): Theleftandrightpointers define the current window.rightexpands the window, andleftcontracts it when a condition (like uniqueness) is violated. -
unordered_setfor Uniqueness Checks: Anunordered_set(orHashSetin other languages) is your best friend for quickly checking if an element exists and maintaining a collection of unique items. Its average O(1) time complexity forinsert,find, anderaseis crucial for optimizing the sliding window. - Understanding
whilevs.if: Thewhileloop for shrinking the window (while (charSet.find(s[right]) != charSet.end())) is critical. It ensures that all instances of the duplicate character (and any characters preceding it) are removed from theleftuntils[right]is truly unique in the current window. Anifhere would only remove one character and might leave other duplicates, leading to incorrect results.
8. Submission Details
- Author Account: ahB1IK4YXF
- Publishing Time: 2026-05-31 22:54:05
Top comments (0)