Minimum Window Substring

Problem

Given two strings s and t, return the minimum window substring of s such that every character in t (including duplicates) is included in the window. If no such substring exists, return an empty string.

  • 1 ≤ s.length, t.length ≤ 10⁵
  • s and t consist of English letters.

Example

Input: s = "ADOBECODEBANC", t = "ABC"
Output: "BANC"

A brute-force approach would check every substring of s to see if it contains all characters of t, which is O(n^3) and infeasible for large inputs. The optimal approach uses a sliding window to expand and contract over s, tracking character counts to efficiently find the smallest valid window. Starting with pointers at the beginning, the window expands until it contains all characters of t. Then it contracts from the left to remove unnecessary characters, updating the minimum window length. This process repeats until the right pointer reaches the end of s. The critical insight is maintaining counts of required characters and only contracting when the window is valid, enabling an O(n) time solution.

Approach

Straightforward Solution

A brute-force approach enumerates all substrings of s and checks if each contains t, resulting in O(n^3) time due to substring generation and character counting, which is too slow.

Core Observation

The problem reduces to finding the smallest substring of s that contains all characters of t with correct multiplicities. This requires tracking character frequencies and efficiently checking window validity.

Path to Optimal

Recognizing the problem as a substring containment problem with multiplicities suggests a sliding window approach. By maintaining two hash maps—one for required character counts and one for the current window counts—and two pointers defining the window boundaries, the algorithm can expand the window to include all required characters and then contract it to find the minimal valid window. This approach reduces the complexity to O(n) because each character is visited at most twice (once when expanding and once when contracting).

Optimal Approach

Use a sliding window with two pointers. Expand the right pointer to include characters until the window contains all characters of t. Then, move the left pointer to shrink the window while maintaining validity. Keep track of the smallest window found. Return the substring corresponding to this window or an empty string if none exists.

Time

O(n)

Each character in s is visited at most twice: once when the right pointer expands the window and once when the left pointer contracts it. Hash map operations are O(1) on average, resulting in linear time complexity.

Space

O(m)

Auxiliary space is used for two hash maps storing character counts: one for t and one for the current window. The size of these maps is bounded by the number of unique characters in t, which is at most m = len(t). This space is necessary to track character frequencies.

Pattern Spotlight

Sliding Window (Variable Size with Frequency Counting)

When searching for a minimum or maximum substring containing all required elements, maintain counts of needed characters and expand the window until valid, then contract from the left to discard unnecessary characters, ensuring each character is processed at most twice.

Solution

1class Solution:
2 def minWindow(self, s: str, t: str) -> str:
3 if t == "":
4 return ""
5
6 count_t = {}
7 for c in t:
8 count_t[c] = 1 + count_t.get(c, 0)
9
10 have, need = 0, len(count_t)
11 res, res_len = [-1, -1], float("infinity")
12 l = 0
13 window = {}
14
15 for r in range(len(s)):
16 c = s[r]
17 window[c] = 1 + window.get(c, 0)
18
19 if c in count_t and window[c] == count_t[c]:
20 have += 1
21
22 while have == need:
23 if (r - l + 1) < res_len:
24 res = [l, r]
25 res_len = (r - l + 1)
26
27 window[s[l]] -= 1
28 if s[l] in count_t and window[s[l]] < count_t[s[l]]:
29 have -= 1
30 l += 1
31
32 l, r = res
33 return s[l:r + 1] if res_len != float("infinity") else ""

Step-by-Step Solution

1

Build Frequency Map for Target String and Initialize Sliding Window State

6count_t = {}
7for c in t:
8 count_t[c] = 1 + count_t.get(c, 0)
10have, need = 0, len(count_t)
11res, res_len = [-1, -1], float("infinity")
12l = 0
13window = {}

Objective

To count the frequency of each character in t and set up variables to track the sliding window and result.

Key Insight

Counting the required characters upfront allows the algorithm to know exactly when the window contains all needed characters. Initializing counters for how many unique characters have been satisfied ('have') versus how many are needed ('need') enables efficient validity checks. The window map tracks the current counts of characters in the sliding window, and the result variables store the best window found so far.

Interview Quick-Check

Core Logic

Building a frequency map for t enables constant-time checks of whether the current window meets the required character counts.

State & Boundaries

Initialize 'have' to 0 and 'need' to the number of unique characters in t to track window validity.

Common Pitfalls & Bugs

Forgetting to initialize the window map or result variables correctly can lead to incorrect window tracking or failure to update the minimal window.

2

Expand the Window by Moving Right Pointer and Update Counts

15for r in range(len(s)):
16 c = s[r]
17 window[c] = 1 + window.get(c, 0)
19 if c in count_t and window[c] == count_t[c]:
20 have += 1

Objective

To iterate over s with the right pointer, expanding the window and updating character counts to track when the window satisfies the requirements.

Key Insight

As the right pointer moves, characters are added to the window map. When the count of a character in the window matches the required count in t, the 'have' counter increments, indicating one more required character is fully satisfied. This incremental tracking avoids rechecking the entire window for validity, enabling efficient detection of valid windows.

Interview Quick-Check

Core Logic

Incrementing the count of the current character and updating 'have' only when the count matches the required frequency ensures accurate tracking of window validity.

State & Boundaries

The right pointer iterates over all characters in s, expanding the window one character at a time.

Common Pitfalls & Bugs

Failing to check if the character is in the target frequency map before updating 'have' can cause incorrect validity tracking.

3

Contract the Window by Moving Left Pointer to Find Minimum Valid Substring

22while have == need:
23 if (r - l + 1) < res_len:
24 res = [l, r]
25 res_len = (r - l + 1)
27 window[s[l]] -= 1
28 if s[l] in count_t and window[s[l]] < count_t[s[l]]:
29 have -= 1
30 l += 1

Objective

To shrink the window from the left while it remains valid, updating the result with the smallest window found.

Key Insight

Once the window contains all required characters ('have' == 'need'), the algorithm attempts to minimize the window size by moving the left pointer forward. Decreasing the count of the leftmost character may invalidate the window if it falls below the required count, so 'have' is decremented accordingly. This careful contraction ensures the minimal valid window is found without missing any potential smaller windows.

Interview Quick-Check

Core Logic

Contracting the window while maintaining validity and updating the minimal window length ensures the algorithm finds the smallest substring containing all characters.

State & Boundaries

The while loop continues as long as the window is valid, allowing progressive shrinking from the left.

Common Pitfalls & Bugs

Not updating 'have' when the count of a character falls below the required frequency leads to incorrect window validity checks.

4

Return the Minimum Window Substring or Empty String if None Found

32l, r = res
33return s[l:r + 1] if res_len != float("infinity") else ""

Objective

To extract and return the substring corresponding to the smallest valid window found, or return an empty string if no valid window exists.

Key Insight

After processing the entire string, the result indices represent the boundaries of the minimal window. If no valid window was found, the result length remains infinity, and an empty string is returned. This final step translates the indices into the required output format.

Interview Quick-Check

Core Logic

Returning the substring using the stored indices ensures the correct minimal window is output.

State & Boundaries

Check if the result length is still infinity to determine if a valid window was found.

Line Analysis

This solution has 4 Critical lines interviewers watch for.

Line 10 Critical
have, need = 0, len(count_t)

Initialize 'have' and 'need' counters for window validity.

'have' tracks how many unique characters currently meet required counts, while 'need' is the total unique characters needed; this enables efficient validity checks.

Line 19 Critical
if c in count_t and window[c] == count_t[c]:

Check if current character count matches required count in t.

Only when the window count equals the required count for a character does it contribute to satisfying the overall requirement, so this condition triggers incrementing 'have'.

Line 22 Critical
while have == need:

While the window is valid, attempt to contract it from the left.

Contracting the window when valid helps find the minimal window containing all required characters by removing unnecessary characters from the left.

Line 28 Critical
if s[l] in count_t and window[s[l]] < count_t[s[l]]:

Check if the removed character is required and if its count falls below required.

If the count of a required character falls below its needed frequency, the window is no longer valid, so 'have' must be decremented.

Line 6 Core
count_t = {}

Initialize a dictionary to count characters in t.

This data structure is essential for tracking the required frequency of each character, enabling constant-time lookups during window expansion.

Line 8 Core
count_t[c] = 1 + count_t.get(c, 0)

Increment the count of the current character in count_t.

Accurately tracking the frequency of each character in t is critical for validating window contents later.

Line 13 Core
window = {}

Initialize a dictionary to count characters in the current window.

Tracking the frequency of characters in the current window is necessary to compare against required counts and determine window validity.

Line 17 Core
window[c] = 1 + window.get(c, 0)

Increment the count of the current character in the window.

Updating the window's character counts allows tracking how many times each character appears, which is essential for validity checks.

Line 20 Core
have += 1

Increment 'have' when a required character's count is met.

Tracking how many unique characters meet their required counts enables quick checks of window validity without scanning all characters.

Line 27 Core
window[s[l]] -= 1

Decrease the count of the character at the left pointer in the window.

Removing the leftmost character from the window updates counts to reflect the contracted window, which may invalidate the window if counts fall below requirements.

Line 29 Core
have -= 1

Decrement 'have' when window no longer satisfies a required character count.

Maintaining accurate 'have' count ensures the algorithm knows when the window loses validity, preventing incorrect minimal window updates.

Line 33 Core
return s[l:r + 1] if res_len != float("infinity") else ""

Return the minimal window substring or empty string if none found.

This final step produces the required output, returning the minimal valid substring or empty string if no valid window exists.

Line 3 Core
if t == "":

Return an empty string if t is empty.

An empty target string means no characters are required, so the minimal window is trivially empty; this early return handles this edge case efficiently.

Line 11 Core
res, res_len = [-1, -1], float("infinity")

Initialize result indices and minimal window length.

Storing the best window found so far allows updating the result when smaller valid windows are discovered.

Line 15 Core
for r in range(len(s)):

Iterate over s with the right pointer to expand the window.

The right pointer moves forward to include new characters, enabling exploration of all possible windows.

Line 23 Core
if (r - l + 1) < res_len:

Check if current window length is smaller than the best found.

Updating the result only when a smaller valid window is found ensures the minimal window is recorded.

Line 24 Core
res = [l, r]

Update result indices to current window boundaries.

Storing the current minimal window boundaries allows returning the correct substring at the end.

Line 25 Core
res_len = (r - l + 1)

Update the minimal window length to current window size.

Tracking the smallest window length found so far is necessary to compare future windows and maintain the minimal result.

Line 30 Core
l += 1

Move the left pointer forward to contract the window.

Advancing the left pointer shrinks the window, enabling the search for smaller valid windows.

Line 4 Standard
return ""

Return immediately with empty string for empty t.

This prevents unnecessary computation when the target is empty, ensuring correctness and efficiency.

Line 7 Standard
for c in t:

Iterate over each character in t to build frequency map.

Counting each character's occurrences in t is necessary to know the exact requirements for a valid window.

Line 12 Standard
l = 0

Initialize left pointer of the sliding window.

The left pointer marks the start of the current window and moves forward to contract the window when possible.

Line 16 Standard
c = s[r]

Get the current character at the right pointer.

Accessing the current character is necessary to update window counts and check validity.

Line 32 Standard
l, r = res

Unpack the result indices into left and right boundaries.

Extracting the stored indices prepares for substring extraction or empty string return.

Test Your Understanding

Why does the algorithm only contract the window when it contains all required characters, and how does this ensure the minimal window is found?

Contracting the window only when it contains all required characters ensures the window remains valid. By shrinking from the left, the algorithm discards unnecessary characters, progressively finding smaller valid windows. This process guarantees that the minimal window containing all characters of t is found because any smaller window would exclude a required character, invalidating the window.

Related Problems

Sliding Window pattern

Don't just read it. Drill it.

Reconstruct Minimum Window Substring from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Minimum Window Substring drill

or drill a free problem