35. Minimum Window Substring
Problem Statement
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 test cases will be generated such that the answer is unique.
A substring is a contiguous sequence of characters within a string.
Example 1:
Input: s = "ADOBECODEBANC", t = "ABC" Output: "BANC" Explanation: The minimum window substring "BANC" includes 'A', 'B', and 'C' from string t.
Example 2:
Input: s = "a", t = "a" Output: "a" Explanation: The entire string s is the minimum window.
Example 3:
Input: s = "a", t = "aa" Output: "" Explanation: Both 'a's from t must be included in the window. Since s only has one 'a', no such window exists.
Solution
from collections import Counter
class Solution:
def minWindow(self, s: str, t: str) -> str:
if not t or not s:
return ""
# Dictionary to store frequency of characters in t
dict_t = Counter(t)
# Number of unique characters in t that must be present in window
required = len(dict_t)
# left and right pointers for the sliding window
left = 0
# formed is the number of unique characters in the current window
# that match the required count in dict_t.
formed = 0
# Dictionary to store frequency of characters in the current window
window_counts = {}
# ans tuple of the form (window length, left, right)
ans = float('inf'), None, None
for right in range(len(s)):
character = s[right]
window_counts[character] = window_counts.get(character, 0) + 1
# If the current character's count in window matches its count in t
if character in dict_t and window_counts[character] == dict_t[character]:
formed += 1
# Try to contract the window from the left
while left <= right and formed == required:
character = s[left]
# Update the minimum window found so far
if right - left + 1 < ans[0]:
ans = (right - left + 1, left, right)
# Remove character from the left of the window
window_counts[character] -= 1
if character in dict_t and window_counts[character] < dict_t[character]:
formed -= 1
left += 1
return "" if ans[0] == float('inf') else s[ans[1] : ans[2] + 1]
Explanation
This problem is a classic application of the sliding window technique.
Core Idea: We expand the window from the right until it contains all characters of t
. Once it does, we try to shrink it from the left to find the smallest valid window. We keep track of the best (smallest) window found so far.
-
Character Frequencies:
dict_t
: Stores the frequency of each character in stringt
.window_counts
: Stores the frequency of each character in the current sliding window.
-
required
andformed
:required
: The number of unique characters fromt
that we need to have in our window (with at least their required frequencies).formed
: The number of unique characters in the current window that satisfy their frequency requirement fromdict_t
.
Algorithm:
-
Expand Window (Right Pointer): The
right
pointer iterates throughs
, adding characters towindow_counts
. If a characters[right]
is indict_t
and its count inwindow_counts
matchesdict_t
, we incrementformed
. -
Contract Window (Left Pointer): Once
formed == required
(meaning the current window contains all characters oft
with their required frequencies), we enter awhile
loop to try and shrink the window from theleft
.- Inside this loop, we first check if the current window is smaller than the
ans
found so far and updateans
if it is. - Then, we remove
s[left]
fromwindow_counts
and incrementleft
. Ifs[left]
was a character fromt
and its count inwindow_counts
drops below its required count indict_t
, we decrementformed
.
- Inside this loop, we first check if the current window is smaller than the
-
The
while
loop continues as long asformed == required
, meaning we keep shrinking the window from the left until it becomes invalid (i.e.,formed < required
). At that point, we resume expanding the window from the right.
This approach ensures that we find the minimum window. The time complexity is O(S + T) where S is the length of s
and T is the length of t
, because both pointers traverse s
at most once, and Counter(t)
takes O(T). The space complexity is O(alphabet_size) for the dictionaries.