Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 27

Minimum Window Substring: Sliding Window & HashMap

Recommended resources

Hello! In our last lesson, we mastered the variable-size sliding window by solving the "Longest Substring Without Repeating Characters" problem. You learned how to expand a window and then efficiently jump the left pointer forward using a HashMap to maintain a valid state.

Today, we'll tackle a more intricate problem that builds directly on this foundation: the Minimum Window Substring. This is a classic, challenging problem frequently seen in technical interviews for top companies. It requires a robust understanding of the sliding window pattern, coupled with careful frequency tracking. Our goal is to solve this problem by first understanding the brute-force logic and then developing an optimal sliding window solution using HashMaps, just as a seasoned engineer would.

1. Understanding the Problem

The task is to find the smallest contiguous substring within a larger string s that contains all the characters of another string t. Importantly, this includes handling duplicate characters. If t is "AAB", the substring in s must contain at least two 'A's and one 'B'.

Let's look at a canonical example:

  • s = "ADOBECODEBANC"
  • t = "ABC"

The smallest substring in s containing 'A', 'B', and 'C' is "BANC". Notice that the order doesn't matter, and there can be other characters in the window (like 'N'). The challenge is to find this minimal window efficiently.

Minimum Window substring - Tutorial

This tutorial from the "takeuforward" site, which is the basis for the TUF DSA sheet, provides a clear definition of the problem.

Read the Problem Statement and Examples to formalize your understanding.


2. The Brute-Force Approach: A Starting Point

As we always do, let's start with the most straightforward, intuitive solution. The problem asks for the "smallest substring," so a natural first thought is to examine all possible substrings of s.

The algorithm would look like this:

  1. Generate every single substring of s. This can be done with nested loops, where the outer loop sets the start index i and the inner loop sets the end index j.
  2. For each generated substring, we must verify if it's a "valid" window—that is, if it contains all characters from t with the required frequencies.
  3. To perform this check, we can use two HashMaps: one to store the frequency of characters in t, and another to store the frequency of characters in the current substring from s. We then compare the maps.
  4. If a substring is valid, we compare its length to the minimum length found so far and update it if we've found a new, smaller valid window.

This method guarantees a correct answer but is highly inefficient. Generating all substrings and repeatedly checking their validity involves a lot of redundant computation.

This image shows how a brute-force method might start by generating and checking all substrings beginning at index 0. This process would be repeated for every possible starting index, highlighting the inefficiency.

Minimum Window substring - Tutorial

The takeuforward tutorial breaks down this brute-force logic and its complexity.

Focus on the Brute Force Approach section. Read the algorithm description and review the corresponding Java code. Pay close attention to the containsAll helper method, which is the core of the validation step. Finally, consider the time complexity analysis provided; it's the reason we must find a better way.


3. The Optimal Solution: A Two-Pointer Sliding Window

The brute-force approach is slow because it re-scans and re-computes frequencies for overlapping substrings. The sliding window technique elegantly solves this by maintaining a single, dynamic window and adjusting its boundaries.

The core strategy is:

  1. Expand the window by moving the right pointer. Keep adding characters to the window until it becomes valid (i.e., contains all required characters from t).
  2. Once the window is valid, shrink it from the left by moving the left pointer. We do this to find the smallest possible valid window. We continue shrinking as long as the window remains valid.
  3. When shrinking makes the window invalid, we go back to step 1 and expand again.

To implement this efficiently, we need a few key components:

  • A HashMap (targetFreq) to store the character frequencies of string t.
  • A second HashMap (windowFreq) to store the character frequencies within our current sliding window.
  • Two pointers, left and right, initialized to 0.
  • A counter mechanism to tell us when our window is valid. A great way to do this is to have a required variable (the number of unique characters in t) and a formed variable (the number of unique characters in our window that meet or exceed the required frequency). The window is valid when formed == required.

Let's walk through the logic:

// Conceptual Variables
Map<Character, Integer> targetFreq; // Pre-calculated from string t
Map<Character, Integer> windowFreq; // For the current window s[left..right]

int required = targetFreq.size();
int formed = 0;

int left = 0;
int minLength = Integer.MAX_VALUE;
int resultStart = 0;

for (int right = 0; right < s.length(); right++) {
    // 1. EXPAND the window to the right
    char charRight = s.charAt(right);
    windowFreq.put(charRight, windowFreq.getOrDefault(charRight, 0) + 1);

    // 2. CHECK if adding charRight helped us form a required character
    if (targetFreq.containsKey(charRight) && 
        windowFreq.get(charRight).intValue() == targetFreq.get(charRight).intValue()) {
        formed++;
    }

    // 3. SHRINK the window from the left once it's valid
    while (left <= right && formed == required) {
        // A valid window is found. Update our result.
        if (right - left + 1 < minLength) {
            minLength = right - left + 1;
            resultStart = left;
        }

        // Try to shrink further
        char charLeft = s.charAt(left);
        windowFreq.put(charLeft, windowFreq.get(charLeft) - 1);

        // 4. CHECK if removing charLeft broke the validity
        if (targetFreq.containsKey(charLeft) && 
            windowFreq.get(charLeft) < targetFreq.get(charLeft)) {
            formed--;
        }

        left++; // Move the left pointer
    }
}

This interplay between expanding the window to satisfy a condition and contracting it to optimize the result is the heart of the sliding window pattern for this type of problem.

The image below from the takeuforward tutorial illustrates this process of expanding and then shrinking.

The window first expands to include "aabdec", at which point it contains all characters of `t`="abc". Then, the shrinking phase begins from the left to find the minimal valid window, which is "abdec".

Now, let's study the complete implementation and the detailed algorithm steps.

Minimum Window substring - Tutorial

The same tutorial provides an excellent walkthrough of the optimal approach.

Read the Optimal Approach section. First, internalize the high-level algorithm. Then, carefully study the Java code provided. Trace the logic with the example s = "ADOBECODEBANC" and t = "ABC" in mind. Finally, review the time and space complexity to see why this is a massive improvement.

To see this logic animated, watching a visual dry run can be incredibly helpful. Striver's video provides exactly that.

L12. Minimum Window Substring | 2 Pointers and Sliding Window Playlist

In this video from the take U forward channel, Striver provides a detailed whiteboard walkthrough of the sliding window logic.

Watch the segment from this walkthrough. Pay close attention to how he uses a map (or hash array) to track frequencies, how the count variable changes, and how the L and R pointers expand and shrink the window to find the optimal answer.


Conclusion

In this lesson, we tackled the "Minimum Window Substring" problem, a significant step up in complexity within the sliding window paradigm. By following our structured approach, we moved from an inefficient brute-force method to a linear-time optimal solution.

Key Takeaways:

  • Problem: Find the smallest substring in s that contains all characters (including duplicates) from t.
  • Pattern: The problem is solved using a variable-size sliding window with a distinct "expand-then-contract" rhythm.
  • Expansion: The window grows by moving the right pointer until it becomes valid (contains all required characters).
  • Contraction: Once valid, the window shrinks from the left to find the smallest possible size while maintaining validity.
  • Data Structures: Two HashMaps are used to efficiently track the required character frequencies (t) and the current window's frequencies (s).
  • Complexity: The optimal sliding window solution achieves a time complexity of O(N+M) and space complexity of O(M), making it highly efficient for interviews.

In our next lesson, we will apply the sliding window technique to another problem: counting the number of substrings with at most K distinct characters. This will reinforce your understanding of the pattern while introducing a slightly different constraint to work with.