L3. Longest Substring Without Repeating Characters | 2 Pointers and Sliding Window Playlist
This chapter explores how to solve the classic Longest Substring Without Repeating Characters problem. We will journey from a naive brute-force approach using nested loops and hashing to an optimized solution utilizing a sliding window pattern with two pointers and a character frequency/index tracking array.
1. Problem Statement & Definition
You are given a string containing any combination of characters from the 256 possible ASCII characters (lowercase, uppercase, special characters, digits). Your goal is to find the length of the longest substring that contains no repeating characters.
Core Definitions:
- Substring: A contiguous sequence of characters within a string. For example, in
"cadbz","cad"is a valid substring, whereas"ca"and"c"is valid, but"cac"or"ca...z"(discontinuous) is not. - Goal: Return the maximum length of such a substring, not the substring itself.
String: c a d b z a b c d
| |
L R (Valid substring window: length 5)
2. Naive Brute Force Approach ()
To understand why optimization is needed, let's examine the brute-force strategy. We can systematically generate every possible contiguous substring starting from each index and expanding with a second pointer .
Brute Force Algorithm Idea:
- Initialize an outer pointer from index to .
- Initialize an inner pointer starting at , extending to the right.
- Maintain a hash structure (or frequency array) to check for character repetition.
- The moment a character repeats within the current window, terminate the inner loop (since expanding further will only maintain or worsen the repetition).
- Track the maximum length encountered.
Brute-Force Pseudo-Code:
max_length = 0
for i in range(n):
# Hash array for 256 ASCII characters
hash_array = [0] * 256
for j in range(i, n):
char_code = ord(s[j])
if hash_array[char_code] == 1:
break # Repeating character found; stop expanding this window
current_length = j - i + 1
max_length = max(max_length, current_length)
hash_array[char_code] = 1
return max_length
Complexity of Naive Approach:
- Time Complexity: because we explore all possible starting points and expand across the string.
- Space Complexity: for the fixed-size ASCII hash array.
3. The Need for Optimization: Two Pointers & Sliding Window
An interviewer will typically reject the brute-force solution for large strings. We need an approach that runs closer to linear time, i.e., .
Core Mental Model / Analogy
Real-Life Analogy: Imagine sliding a transparent viewing window across a ticker tape of letters. Your right hand (
R) moves forward to pull new letters into the window. Your left hand (L) only moves forward when a duplicate letter appears inside your window, squeezing the window from the left until the duplicate is dropped out.
The sliding window pattern is not a rigid rigid algorithm; it is a constructive problem-solving template where the window expands and contracts dynamically based on constraints.
4. Step-by-Step Sliding Window Dry Run
Let's trace the optimized two-pointer approach on the string: "cadbza"
-
Initialization:
L = 0,R = 0,max_length = 0- Hash array of size 256 initialized to
-1(storing the last seen index of each character).
-
Expanding R:
R = 0(char'c'): Not in hash. Length =0 - 0 + 1 = 1. Updatemax_length = 1. Mark hash of'c'to index0.R = 1(char'a'): Not in hash. Length =1 - 0 + 1 = 2. Updatemax_length = 2. Mark hash of'a'to index1.R = 2(char'd'): Not in hash. Length =3. Updatemax_length = 3. Mark hash of'd'to index2.R = 3(char'b'): Not in hash. Length =4. Updatemax_length = 4. Mark hash of'b'to index3.R = 4(char'z'): Not in hash. Length =5. Updatemax_length = 5. Mark hash of'z'to index4.
-
Encountering a Duplicate (
R = 5, char'a'):- We check hash for
'a'. It was previously seen at index1. - Is this past occurrence inside our current active window
[L, R]? Yes (1 >= L). - Therefore, we must jump our left pointer
Ltohash['a'] + 1, which is1 + 1 = 2. - Window contracts:
Lmoves from0to2. - Update hash for
'a'to current index5.
- We check hash for
5. Optimized Algorithm Implementation
Here is the clean, production-ready logic for the optimized sliding window approach:
public class LongestSubstringWithoutRepeating {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
if (n == 0) return 0;
int maxLen = 0;
int l = 0, r = 0;
// Hash array to store the last seen index of each ASCII character
int[] hash = new int[256];
Arrays.fill(hash, -1);
while (r < n) {
char currentChar = s.charAt(r);
int charCode = (int) currentChar;
// Check if character exists in map and lies within the current window [l, r]
if (hash[charCode] != -1 && hash[charCode] >= l) {
l = hash[charCode] + 1;
}
// Calculate current window length
int length = r - l + 1;
maxLen = Math.max(maxLen, length);
// Update the last seen index of the character and expand right pointer
hash[charCode] = r;
r++;
}
return maxLen;
}
}
6. Edge Cases & Debugging Real-World Pitfalls
When implementing sliding window algorithms in production or coding interviews, pay close attention to these common pitfalls:
7. Complexity Analysis
- Time Complexity:
- The right pointer
rtraverses the string from index to exactly once. - The left pointer
lonly moves forward and never backtracks. Across the entire execution,landrtraverse at most steps.
- The right pointer
- Space Complexity:
- The frequency hash array is fixed at size 256 regardless of the input string length , consuming constant auxiliary space.
Summary and Key Takeaways
- Problem Definition: Finding the longest contiguous substring without repeating characters requires tracking uniqueness dynamically.
- Sub-Optimal vs Optimal: Brute-force generation of all substrings takes , whereas the sliding window approach optimizes this to linear time.
- Two-Pointer Dynamics: The right pointer (
r) expands the window continuously, while the left pointer (l) contracts the window conditionally when duplicates occur. - Index-Based Hashing: Instead of storing boolean flags or clearing frequency maps, storing the exact last-seen index in an integer array allows
lto jump instantly rather than step-by-step. - Window Range Check: Always ensure that a character's stored historical index falls within the active window (
>= l) before adjusting the left boundary. - Constant Space: Utilizing a fixed-size array of 256 handles all standard ASCII characters in space complexity.