3. Longest Substring Without Repeating Characters: A Complete Guide
Introduction
The problem of finding the longest substring without repeating characters is a classic algorithmic challenge that tests understanding of string manipulation, data structures, and optimization techniques. Given a string, the goal is to determine the maximum length of a contiguous sequence of characters where no character repeats. This problem is frequently encountered in coding interviews and competitive programming, making it essential to grasp its solution thoroughly And it works..
Understanding the Problem
A substring is a contiguous sequence of characters within a string. As an example, in the string "abcabcbb," valid substrings include "abc," "bca," and "abcbb." The task here is to identify the longest substring where all characters are unique That's the part that actually makes a difference..
Key Concepts
- Uniqueness: Each character in the substring must appear only once.
- Contiguity: Characters must appear in consecutive positions.
- Case Sensitivity: Uppercase and lowercase letters are treated as distinct (e.g., "A" ≠ "a").
To give you an idea, the longest substring in "abcabcbb" is "abc" (length 3), while in "abba," it is "ab" or "ba" (length 2) Nothing fancy..
Approaches to Solve the Problem
1. Brute Force Method
The simplest approach involves checking all possible substrings and tracking the longest one with unique characters Easy to understand, harder to ignore. And it works..
Steps:
- Iterate over all starting indices of the string.
- For each starting index, expand the substring to the right.
- Use a set to track characters in the current substring.
- If a duplicate is found, stop and reset the set.
- Update the maximum length accordingly.
Time Complexity:
- O(n³): Nested loops (to generate substrings) and checking uniqueness (O(n) per substring).
This method is inefficient for large strings, prompting the need for optimized solutions.
2. Sliding Window Technique
The sliding window approach efficiently narrows down valid substrings by maintaining a dynamic "window" of characters Simple as that..
Concept:
- Use two pointers,
leftandright, to represent the current window. - Expand the window by moving
rightand contract it when duplicates are found.
Steps:
- Initialize
left = 0andmax_length = 0. - Use a set to track characters in the current window.
- Iterate with `
right pointer through the string.
Also, 4. Even so, if the character at right is not in the set, add it and update max_length. 5. In real terms, if a duplicate is found, remove the character at left from the set and increment left. 6. Repeat until right reaches the end of the string.
Time Complexity:
- O(n): Each character is visited at most twice—once by
rightand once byleft.
Example Walkthrough:
Consider the string "pwwkew":
- Start:
left=0,right=0, window={p}, max_length=1 - Move
right: window={p,w}, max_length=2 - Move
right: duplicatewfound → removep, moveleftto 1, then removew, moveleftto 2 - Continue expanding: window=
{w,k}, then{w,k,e}, max_length=3 - Final result: 3
3. Optimized Sliding Window with Hash Map
While the set-based sliding window works well, we can further optimize by jumping the left pointer directly to the correct position using a hash map (dictionary) And that's really what it comes down to..
Concept:
- Store each character's latest index in a dictionary.
- When a duplicate is encountered, move
leftdirectly toindex[character] + 1instead of incrementing one by one.
Steps:
- Initialize
left = 0,max_length = 0, and an empty dictionarychar_index. - Iterate
rightfrom 0 ton-1. - If
s[right]exists inchar_indexand its stored index is ≥left, updatelefttochar_index[s[right]] + 1. - Update
char_index[s[right]] = right. - Update
max_length = max(max_length, right - left + 1).
Time Complexity:
- O(n): Single pass through the string with constant-time dictionary lookups.
Space Complexity:
- O(min(n, m)): Where
mis the size of the character set (e.g., 26 for lowercase English letters, 128 for ASCII).
Code Implementations
Python
def length_of_longest_substring(s: str) -> int:
char_index = {}
left = 0
max_length = 0
for right in range(len(s)):
if s[right] in char_index and char_index[s[right]] >= left:
left = char_index[s[right]] + 1
char_index[s[right]] = right
max_length = max(max_length, right - left + 1)
return max_length
JavaScript
function lengthOfLongestSubstring(s) {
let charIndex = new Map();
let left = 0;
let maxLength = 0;
for (let right = 0; right < s.get(s[right]) + 1;
}
charIndex.get(s[right]) >= left) {
left = charIndex.has(s[right]) && charIndex.length; right++) {
if (charIndex.set(s[right], right);
maxLength = Math.
return maxLength;
}
Java
import java.util.HashMap;
public int lengthOfLongestSubstring(String s) {
HashMap charIndex = new HashMap<>();
int left = 0;
int maxLength = 0;
for (int right = 0; right < s.length(); right++) {
if (charIndex.containsKey(s.charAt(right)) && charIndex.get(s.charAt(right)) >= left) {
left = charIndex.get(s.In practice, charAt(right)) + 1;
}
charIndex. put(s.charAt(right), right);
maxLength = Math.
return maxLength;
}
Common Pitfalls and Edge Cases
- Empty String: The function should return 0. Both the optimized and set-based approaches handle this naturally since no iteration occurs.
- All Unique Characters: A string like
"abcdef"should return the full length of the string. - All Identical Characters: A string like
"aaaa"should return 1, since no substring longer than a single character can be non-repeating. - Case Sensitivity: check that
"A"and"a"are treated as different characters unless the problem explicitly states otherwise
Testing Your Solution
Before submitting or deploying your solution, it's essential to validate it against a variety of test cases. Here's a recommended testing checklist:
Basic Tests
assert length_of_longest_substring("") == 0
assert length_of_longest_substring("abcabcbb") == 3 # "abc"
assert length_of_longest_substring("bbbbb") == 1 # "b"
assert length_of_longest_substring("pwwkew") == 3 # "wke"
assert length_of_longest_substring("abcdef") == 6 # all unique
Advanced Tests
# Mixed case sensitivity
assert length_of_longest_substring("aA") == 2 # "aA" — treated as distinct
# Special characters and spaces
assert length_of_longest_substring("a b c a") == 4 # " b c"
# Long input with repeating pattern
assert length_of_longest_substring("abba") == 2 # "ab" or "ba"
# Single character
assert length_of_longest_substring("z") == 1
These tests cover not only the standard cases but also scenarios involving spaces, special characters, and tricky patterns like "abba" where the left pointer must move forward correctly after encountering a repeated character at the right end of the window Most people skip this — try not to..
Variations of This Problem
Once you've mastered the core problem, several closely related challenges build on the same sliding window technique:
-
Longest Substring With At Most K Distinct Characters: Instead of enforcing uniqueness, allow up to
kdistinct characters in the window. The window shrinks when the distinct count exceedsk. -
Minimum Window Substring: Given two strings
sandt, find the smallest substring ofsthat contains all characters oft. This requires tracking character frequencies rather than just indices Simple, but easy to overlook. Still holds up.. -
Longest Repeating Character Replacement: You may replace up to
kcharacters to maximize the length of a repeating character substring. The window expands as long as the number of replacements needed stays within the budget. -
Fruit Into Baskets: A classic problem where you collect fruits of at most two types in contiguous positions — structurally identical to the "at most 2 distinct characters" variant.
All of these share the same foundational pattern: maintain a dynamic window, expand it greedily, and shrink it only when constraints are violated.
Key Takeaways
The sliding window approach is one of the most powerful patterns for solving substring and subarray problems efficiently. Here's a recap of what makes it so effective for this problem:
- Two pointers (
leftandright) define a dynamic window that never needs to backtrack, ensuring linear time complexity. - A hash map provides constant-time lookups to track the most recent position of each character, enabling instant decisions about when to move the left boundary.
- The algorithm is stateful — each step builds on the previous window's information rather than recomputing from scratch.
This technique generalizes far beyond substring problems. So any scenario where you need to find a contiguous segment satisfying certain constraints — whether in strings, arrays, or streams of data — is a candidate for a sliding window solution. Practice recognizing these patterns, and you'll find that a wide family of seemingly distinct problems reduces to the same elegant core idea.