Welcome back! In our previous lesson, we established a strong foundation with the fixed-size sliding window, a pattern that enabled us to solve problems like finding the maximum sum of a fixed-size subarray in linear time. We saw how sliding a window of a constant size avoids redundant computations.
Today, we will build on that concept by exploring a more dynamic and flexible variant: the variable-size sliding window. Unlike its fixed-size counterpart, this window can grow and shrink based on the conditions of the problem. You'll learn to apply this powerful pattern to solve one of the most classic and frequently asked interview questions: finding the "Longest Substring Without Repeating Characters." Following our established learning path, we will progress from a brute-force solution to a highly optimized, linear-time algorithm.
1. Defining the Problem
The problem statement is concise but requires careful thought. Given a string, our goal is to find the length of the longest substring that contains no repeating characters.
Let's break this down with a few examples:
- Input:
s = "abcabcbb"-> Output:3(The longest substring is "abc"). - Input:
s = "bbbbb"-> Output:1(The longest substring is "b"). - Input:
s = "pwwkew"-> Output:3(The longest substring is "wke". Note that "pwke" is a subsequence, not a substring, as it's not contiguous).
This problem is a staple in technical interviews because it effectively tests your ability to recognize patterns, choose appropriate data structures, and optimize your solution—all critical skills for a software development role.
LeetCode Problem #1 — Longest Substring Without Repeating Characters” in Java: From Brute Force to…
This article from Medium provides an excellent overview of the problem.
Please read the introduction and problem statement. Pay close attention to the examples and the "Why This Problem Matters" section, as it frames why this is such a valuable problem to master for your placement preparation.
2. Approach 1: The Brute-Force Solution
Let's begin with the most intuitive approach. The problem asks for the longest valid substring. A straightforward way to tackle this is to generate every single possible substring, check if it contains unique characters, and keep track of the length of the longest valid one we find.
How can we implement this?
- Use a pair of nested loops to generate all substrings. The outer loop (
i) picks a starting character, and the inner loop (j) picks an ending character. - For each generated substring
s[i...j], we need a helper function to verify if all its characters are unique. This helper can use aHashSetto efficiently check for duplicates. If we encounter a character that's already in our set for the current substring, it's not unique. - If the substring is indeed unique, we compare its length (
j - i + 1) with our current maximum length and update it if necessary.
This method is thorough but, as we'll see, computationally expensive.
LeetCode Problem #1 — Longest Substring Without Repeating Characters” in Java: From Brute Force to…
The article we just looked at provides a clear breakdown of this brute-force method.
Read the section titled "Solution 1: Brute Force Approach". Focus on the algorithm steps and the corresponding Java implementation. Finally, analyze the time complexity analysis. An O(n³) solution is rarely acceptable in an interview, which provides the motivation for a better approach.
(Note: A slightly more optimized O(n²) brute-force can be written, as seen in the GeeksforGeeks article, but the core idea of checking all substrings remains inefficient.)
3. A Better Approach: Sliding Window with a HashSet
The brute-force method is slow because it repeatedly re-evaluates overlapping substrings. The sliding window technique offers a way to avoid this. We can maintain a "window" representing our current substring of unique characters and slide it across the string.
Here, the window size is not fixed. It expands when we find a new unique character and shrinks when we encounter a duplicate.
Here is the logic using two pointers, left and right, and a HashSet:
- Initialize
left = 0,right = 0,maxLength = 0, and an emptyHashSet. - Iterate with the
rightpointer from the beginning to the end of the string, expanding the window. - For each character
s.charAt(right), check if it's already in theHashSet.- If it's a duplicate: The window
[left, right]is now invalid. We must shrink the window from the left. In awhileloop, removes.charAt(left)from the set and incrementleftuntil the duplicate is no longer in the set. - If it's unique: The character is fine.
- If it's a duplicate: The window
- Add the current character
s.charAt(right)to theHashSet. - At this point, the window
[left, right]is guaranteed to contain unique characters. Update the max length:maxLength = Math.max(maxLength, right - left + 1).
This approach ensures that each character is processed at most twice (once by right and once by left), leading to a linear time complexity.
LeetCode Problem #1 — Longest Substring Without Repeating Characters” in Java: From Brute Force to…
Let's continue with the same article to see this improved approach.
Read the section "Solution 2: Sliding Window with HashSet". Study the algorithm steps, the Java code, and the complexity analysis. Understanding why the time complexity is O(2n) and not O(n²) is crucial.
4. The Optimal Approach: Sliding Window with a HashMap or Array
We can optimize further. The HashSet approach shrinks the window one character at a time from the left. What if we could jump the left pointer directly to the correct position?
We can achieve this by using a HashMap<Character, Integer> to store not just the character, but its last seen index.
The logic is refined as follows:
- Initialize
left = 0,right = 0,maxLength = 0, and an emptyHashMap. - Iterate with the
rightpointer from0ton-1. - When considering the character
c = s.charAt(right):- Check if
cis in theHashMap. - If it is, it's a potential duplicate. Let its last seen index be
lastIndex = map.get(c). We must ensure ourleftpointer is past this last occurrence. However, we only care if thislastIndexfalls within our current window (lastIndex >= left). So, we updateleft = Math.max(left, lastIndex + 1). ThisMath.maxis a key trick to preventleftfrom ever moving backward.
- Check if
- Update the character's last seen index in the map:
map.put(c, right). - Calculate the current window's length and update the max:
maxLength = Math.max(maxLength, right - left + 1).
Striver provides an excellent, detailed dry run of this exact logic in his video. Watching this will solidify your understanding of how the pointers and the map interact.
L3. Longest Substring Without Repeating Characters | 2 Pointers and Sliding Window Playlist
In this video from the TUF channel, Striver visualizes the optimal sliding window approach.
Watch the detailed dry run from this segment. He uses a "hash" (which you can implement as a HashMap or array) to track last-seen indices. Follow how the left and right pointers move, how the hash is updated, and especially how the left pointer "jumps" when a duplicate is found within the current window.
The image below illustrates this jumping mechanism. When the right pointer encounters the second 'a' (at index 5), the left pointer doesn't just move one step; it jumps to index 1, which is one position after the previous 'a' was seen.
Here is the Java code for this optimal approach.
LeetCode Problem #1 — Longest Substring Without Repeating Characters” in Java: From Brute Force to…
The Medium article clearly implements this HashMap-based optimal solution.
Read "Solution 3: Optimized Sliding Window with HashMap". Go through the algorithm steps and the Java implementation. Make sure you understand the critical line: left = Math.max(left, map.get(currentChar) + 1);.
A Final Optimization: Using an Array
If we know the character set is limited (e.g., ASCII, which has 128 or 256 characters), we can replace the HashMap with a simple integer array of size 128 or 256. The character's ASCII value serves as the index. This is slightly more performant as it avoids the overhead of hashing and potential hash collisions. The logic remains identical.
You can find the Java code for this array-based optimization in the same Medium article under "Solution 4: Array-Based Sliding Window". For most interviews, the HashMap solution is perfectly optimal and more general, but mentioning this array-based optimization demonstrates a deeper level of performance awareness.
Conclusion
In this lesson, we advanced from the fixed-size sliding window to the more versatile variable-size window. We tackled a classic interview problem, progressing from a naive brute-force method to a highly efficient linear-time solution, a thought process highly valued by interviewers.
Key Takeaways:
- Variable-Size Window: This pattern is ideal for finding the longest/shortest substring/subarray that satisfies a certain condition. The window expands and shrinks dynamically.
- Problem: Longest Substring Without Repeating Characters.
- Brute-Force: O(n³) or O(n²) complexity by checking all substrings. Too slow for interviews.
- Better Solution (HashSet): An O(n) sliding window approach where the window shrinks from the left by one element at a time to resolve duplicates.
- Optimal Solution (HashMap/Array): A true O(n) single-pass sliding window where the left pointer intelligently jumps forward to resolve duplicates, avoiding incremental shrinking.
- Data Structures: A
HashSetis good for checking presence, while aHashMapor an integer array is better for the optimal solution as it can store the last-seen index.
In our next lesson, we will continue with the variable-size sliding window pattern and apply it to another challenging and common interview problem: finding the "Minimum Window Substring". This will add another layer of complexity and further cement your understanding of this powerful technique.