Skip to content

Minimum Window Substring

Given two strings s and t of lengths m and n respectively, return the minimum window substring of s such that every character in t (including duplicates) is included in the window. If there is no such substring, return the empty string "".

The testcases will be generated such that the answer is unique.

stOutputExplanation
"ADOBECODEBANC""ABC""BANC"Substring "BANC" has length 4 and contains A, B, C once each
"a""a""a"The entire string is the answer
"a""aa"""String "a" has only one a, but t requires two
  • m == s.length
  • n == t.length
  • 1 <= m, n <= 10^5
  • s and t consist of uppercase and lowercase English letters.
  • It is guaranteed for each test case, there is a unique answer.
ApproachTimeSpaceBest When
Brute ForceO(|S|² * |T|)O(|T|)Input is tiny, learning the basic logic
Sliding WindowO(|S| + |T|)O(|S| + |T|)General case — optimal for interviews and production
  • Pick Sliding Window if you want to master a classic interview pattern that applies to many problems.
  • Pick Brute Force if you are still learning and want to understand the problem before optimizing.
Best For Speed
Sliding Window
O(|S| + |T|) time · O(|S| + |T|) space
Learning
Brute Force
O(|S|² * |T|) time · O(|T|) space
★ Recommended

Use two pointers to maintain a sliding window. Expand the right pointer to include characters until the window contains all required characters from t. Then contract the left pointer to find the minimum valid window. Track character frequencies using a hash map and maintain a count of how many distinct characters in t are satisfied.

The key insight is that we only need to shrink the window when it becomes invalid (we’ve moved the left pointer too far), not after every step. This allows us to achieve linear time complexity.

⏱ Time O(|S| + |T|) Two passes, O(1) amortized per character 💾 Space O(|S| + |T|) Hash maps for char frequency

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

StepleftrightWindowChar CountFormedValid?Action
100”A”{A:1}0/3Expand right
203”ADOBEC”{A:1,B:1,C:1}3/3Contract left
3512”BANC”{B:1,A:1,N:1,C:1}3/3Found minimum

Animated walkthrough

How the sliding window finds the minimum substring

Watch the window expand (right pointer moves) to find valid substrings, then contract (left pointer moves) to minimize length while maintaining validity.

Step 1 of 3 Target: 3
Waiting to begin...
Array state
Memory / lookup state

function minWindowSlidingWindow(s, t):
if s is empty or t is empty or len(s) < len(t):
return ""
dictT = frequency map of characters in t
required = number of unique characters in dictT that need to be satisfied
formed = number of unique characters in current window that are satisfied
windowCounts = frequency map of characters in current window
ansLen = infinity, ansLeft = 0, ansRight = 0
left = 0
for right in range(len(s)):
char = s[right]
windowCounts[char]++
// If frequency of char in window matches that in t, increment formed
if char in dictT and windowCounts[char] == dictT[char]:
formed++
// Contract window from left while it remains valid
while left <= right and formed == required:
char = s[left]
// Update answer if current window is smaller
if right - left + 1 < ansLen:
ansLen = right - left + 1
ansLeft = left
ansRight = right
windowCounts[char]--
if char in dictT and windowCounts[char] < dictT[char]:
formed--
left++
return "" if ansLen == infinity else s[ansLeft:ansRight + 1]
minimum_window_substring_sliding_window.py
from typing import Dict
def min_window_sliding_window(s: str, t: str) -> str:
if not s or not t or len(s) < len(t):
return ""
# Dictionary to store frequency of characters in t
dict_t: Dict[str, int] = {}
for char in t:
dict_t[char] = dict_t.get(char, 0) + 1
required = len(dict_t)
formed = 0
window_counts: Dict[str, int] = {}
# ans tuple of the form (window length, left, right)
ans = float("inf"), None, None
l = 0
for r in range(len(s)):
# Add one character from the right to the window
char = s[r]
window_counts[char] = window_counts.get(char, 0) + 1
# If frequency of current character added equals the desired count in t
# then increment the formed count
if char in dict_t and window_counts[char] == dict_t[char]:
formed += 1
# Try to contract the window until the point where it ceases to be 'desirable'
while l <= r and formed == required:
char = s[l]
# Save the smallest window
if r - l + 1 < ans[0]:
ans = (r - l + 1, l, r)
# The character at the position pointed by the `left` pointer is no longer
# a part of the window
window_counts[char] -= 1
if char in dict_t and window_counts[char] < dict_t[char]:
formed -= 1
# Move the left pointer ahead for the next iteration
l += 1
# Return the smallest window or empty string
return "" if ans[0] == float("inf") else s[ans[1] : ans[2] + 1]
print(min_window_sliding_window("ADOBECODEBANC", "ABC")) # "BANC"
print(min_window_sliding_window("a", "a")) # "a"
print(min_window_sliding_window("a", "aa")) # ""
✓ Simple

Check every possible substring of s. For each substring, count the frequency of characters and verify whether it contains all required characters from t with sufficient counts. Track the minimum valid substring found.

This approach is straightforward but inefficient. It is useful for understanding the problem but impractical for large inputs.

⏱ Time O(|S|² * |T|) All substrings checked, each validated in O(|T|) 💾 Space O(|T|) Character frequency maps

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

StartEndSubstringLengthContains A?Contains B?Contains C?Valid?
06”ADOBEC”6
312”BECODEBANC”10
913”BANC”4✓ → Minimum
function minWindowBruteForce(s, t):
if s is empty or t is empty or len(s) < len(t):
return ""
dictT = frequency map of characters in t
minLen = infinity
minStart = 0
for i in range(len(s)):
for j in range(i + 1, len(s) + 1):
substring = s[i:j]
substringCount = frequency map of characters in substring
// Check if substring contains all required characters
valid = true
for each char in dictT:
if substringCount[char] < dictT[char]:
valid = false
break
// Update minimum if this substring is valid
if valid and len(substring) < minLen:
minLen = len(substring)
minStart = i
return "" if minLen == infinity else s[minStart:minStart + minLen]
minimum_window_substring_brute_force.py
from typing import Dict
def min_window_brute_force(s: str, t: str) -> str:
if not s or not t or len(s) < len(t):
return ""
dict_t: Dict[str, int] = {}
for char in t:
dict_t[char] = dict_t.get(char, 0) + 1
min_len = float("inf")
min_start = 0
# Check all possible substrings
for i in range(len(s)):
for j in range(i + 1, len(s) + 1):
substring = s[i:j]
# Check if substring contains all characters from t with required frequencies
substring_count: Dict[str, int] = {}
for char in substring:
substring_count[char] = substring_count.get(char, 0) + 1
# Verify if this substring is valid
valid = True
for char in dict_t:
if substring_count.get(char, 0) < dict_t[char]:
valid = False
break
# Update minimum if this is a valid and shorter substring
if valid and len(substring) < min_len:
min_len = len(substring)
min_start = i
return s[min_start : min_start + min_len] if min_len != float("inf") else ""
print(min_window_brute_force("ADOBECODEBANC", "ABC")) # "BANC"
print(min_window_brute_force("a", "a")) # "a"
print(min_window_brute_force("a", "aa")) # ""
✓ Simple

A third approach demonstrating an alternative technique for solving this problem. This implementation optimizes either time or space complexity compared to the previous approaches.

⏱ Time O(n) Optimized complexity 💾 Space O(1) Efficient space usage
function approach3():
// Implementation approach goes here
minimum_window_substring_optimized.py
"""
Solution for Minimum Window Substring
"""
def solve():
"""Implementation for approach 3 of Minimum Window Substring"""
pass
if __name__ == "__main__":
print("Solution ready")