Minimum Window Substring
HardPrompt
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.
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 the largest window of s only has one 'a', return empty string.
Constraints:
m == s.lengthn == t.length1 <= m, n <= 105sandtconsist of uppercase and lowercase English letters.
Follow up: Could you find an algorithm that runs in O(m + n) time?
Approaches
3 approaches with complexity analysis and trade-offs.
The most straightforward approach is to check every possible substring of s. For each substring, we verify if it contains all the characters from t. We keep track of the shortest such valid substring found.
Algorithm
-
- Create a frequency map
tFreqfor the stringt.
- Create a frequency map
-
- Initialize
minLengthto a very large value andresultto an empty string.
- Initialize
-
- Iterate with a start pointer
ifrom0tos.length() - 1.
- Iterate with a start pointer
-
- Iterate with an end pointer
jfromitos.length() - 1.
- Iterate with an end pointer
-
- Extract the substring
sub = s.substring(i, j + 1).
- Extract the substring
-
- Check if
subis a valid window by creating a frequency map forsuband comparing it withtFreq.
- Check if
-
- If
subis valid and its length is less thanminLength, updateminLengthandresult.
- If
-
- Return
result.
- Return
Walkthrough
We generate all substrings of s using two nested loops, where i is the start index and j is the end index. For each substring, we create a frequency map of its characters. We also create a frequency map for the target string t. We then compare the two frequency maps. A substring is considered a valid "window" if the frequency of every character in t is less than or equal to its frequency in the substring. If a valid window is found, we compare its length with the minimum length found so far and update it if the current window is shorter. This process continues until all substrings have been checked.
class Solution { public String minWindow(String s, String t) { if (s.length() < t.length()) { return ""; } Map<Character, Integer> tFreq = new HashMap<>(); for (char c : t.toCharArray()) { tFreq.put(c, tFreq.getOrDefault(c, 0) + 1); } int minLength = Integer.MAX_VALUE; String result = ""; for (int i = 0; i < s.length(); i++) { for (int j = i; j < s.length(); j++) { String sub = s.substring(i, j + 1); if (sub.length() >= t.length()) { if (containsAll(sub, tFreq)) { if (sub.length() < minLength) { minLength = sub.length(); result = sub; } } } } } return result; } private boolean containsAll(String sub, Map<Character, Integer> tFreq) { Map<Character, Integer> subFreq = new HashMap<>(); for (char c : sub.toCharArray()) { subFreq.put(c, subFreq.getOrDefault(c, 0) + 1); } for (Map.Entry<Character, Integer> entry : tFreq.entrySet()) { char c = entry.getKey(); int count = entry.getValue(); if (subFreq.getOrDefault(c, 0) < count) { return false; } } return true; }}Complexity
Time
O(m^3)
Space
O(m + k)
Trade-offs
Pros
Simple to conceptualize and implement.
Cons
Extremely inefficient and will not pass the time limits for the given constraints.
This approach improves upon the pure brute force method by avoiding the repeated creation of frequency maps for overlapping substrings. Instead of recalculating the frequency map for each substring from scratch, we can build it incrementally.
Algorithm
-
- Create a frequency map
tFreqfor the stringt.
- Create a frequency map
-
- Initialize
minLengthto a very large value andresultto an empty string.
- Initialize
-
- Iterate with a start pointer
ifrom0tos.length() - 1.
- Iterate with a start pointer
-
- Initialize an empty frequency map
windowFreq.
- Initialize an empty frequency map
-
- Iterate with an end pointer
jfromitos.length() - 1.
- Iterate with an end pointer
-
-
Add the character `s.charAt(j)` to `windowFreq`.
-
-
-
Check if `windowFreq` makes the window valid with respect to `tFreq`.
-
-
-
If the window is valid and its length is the smallest found so far, update the result.
-
-
- Return
result.
- Return
Walkthrough
We still use two nested loops to define the window, with i as the start and j as the end. For a fixed start i, we expand the window by moving j from i to the end of the string. As we expand the window one character at a time (by incrementing j), we update a single frequency map for the current window s[i...j]. At each step, we check if the current window is valid by comparing its frequency map with t's frequency map. If a valid window is found, we record its length and check if it's the new minimum.
class Solution { public String minWindow(String s, String t) { if (s.length() < t.length()) { return ""; } Map<Character, Integer> tFreq = new HashMap<>(); for (char c : t.toCharArray()) { tFreq.put(c, tFreq.getOrDefault(c, 0) + 1); } int minLength = Integer.MAX_VALUE; String result = ""; for (int i = 0; i < s.length(); i++) { Map<Character, Integer> windowFreq = new HashMap<>(); for (int j = i; j < s.length(); j++) { char c = s.charAt(j); windowFreq.put(c, windowFreq.getOrDefault(c, 0) + 1); if (j - i + 1 >= t.length()) { if (isWindowValid(windowFreq, tFreq)) { if (j - i + 1 < minLength) { minLength = j - i + 1; result = s.substring(i, j + 1); } // Optimization: Once a valid window is found for a starting 'i', // any longer window starting at 'i' is not minimal. break; } } } } return result; } private boolean isWindowValid(Map<Character, Integer> windowFreq, Map<Character, Integer> tFreq) { for (Map.Entry<Character, Integer> entry : tFreq.entrySet()) { char c = entry.getKey(); int count = entry.getValue(); if (windowFreq.getOrDefault(c, 0) < count) { return false; } } return true; }}Complexity
Time
O(m^2 * k)
Space
O(k)
Trade-offs
Pros
More efficient than the pure brute-force approach.
Cons
Still inefficient for large inputs and results in a "Time Limit Exceeded" error on most platforms.
The most efficient solution uses the sliding window technique. This approach maintains a "window" (a substring of s) using two pointers, left and right. The window is expanded by moving the right pointer and contracted by moving the left pointer. The goal is to find the smallest window that satisfies the condition.
Algorithm
-
- Create a frequency map
tFreqfort.
- Create a frequency map
-
- Initialize
left = 0,minLength = infinity,minStart = 0.
- Initialize
-
- Initialize
required= number of unique characters int, andformed = 0.
- Initialize
-
- Initialize a frequency map for the window,
windowFreq.
- Initialize a frequency map for the window,
-
- Iterate
rightfrom0tos.length() - 1:
- Iterate
-
- Add
s[right]to the window and updatewindowFreq.
- Add
-
- If
s[right]is intFreqand its count inwindowFreqnow equals its count intFreq, incrementformed.
- If
-
- While the window is valid (
formed == required):
- While the window is valid (
-
-
Update `minLength` and `minStart` if the current window is smaller.
-
-
-
Remove `s[left]` from the window by decrementing its count in `windowFreq`.
-
-
-
If `s[left]` is in `tFreq` and its count just dropped below the required count, decrement `formed`.
-
-
-
Increment `left`.
-
-
- After the loop, if
minLengthis still infinity, no such window exists. Otherwise, return the substring starting atminStartwith lengthminLength.
- After the loop, if
Walkthrough
First, we create a frequency map tFreq for the target string t. We also determine the number of unique characters in t that we need to find in a window, let's call this required. We use two pointers, left and right, both starting at the beginning of s. We also maintain a frequency map for the current window, windowFreq, and a counter formed which tracks how many of the required characters we have "formed" (i.e., their count in the window matches their count in t).
We expand the window by incrementing the right pointer. For each new character added to the window, we update windowFreq. If adding this character causes its count to match the required count from tFreq, we increment formed. Once formed equals required, we have a valid window. Now, we try to make it smaller by contracting it from the left. We increment the left pointer. While contracting, we update the minimum length found so far. When removing a character from the left of the window, we update windowFreq. If this removal causes a character's count to drop below its required count, we decrement formed, and the window is no longer valid. We continue this process of expanding and contracting until the right pointer reaches the end of s. The smallest length recorded during the process gives us the minimum window substring.
class Solution { public String minWindow(String s, String t) { if (s == null || t == null || s.length() == 0 || t.length() == 0 || s.length() < t.length()) { return ""; } Map<Character, Integer> tFreq = new HashMap<>(); for (char c : t.toCharArray()) { tFreq.put(c, tFreq.getOrDefault(c, 0) + 1); } int left = 0; int minLength = Integer.MAX_VALUE; int minStart = 0; int required = tFreq.size(); int formed = 0; Map<Character, Integer> windowFreq = new HashMap<>(); for (int right = 0; right < s.length(); right++) { char c = s.charAt(right); windowFreq.put(c, windowFreq.getOrDefault(c, 0) + 1); if (tFreq.containsKey(c) && windowFreq.get(c).intValue() == tFreq.get(c).intValue()) { formed++; } while (left <= right && formed == required) { if (right - left + 1 < minLength) { minLength = right - left + 1; minStart = left; } char leftChar = s.charAt(left); windowFreq.put(leftChar, windowFreq.get(leftChar) - 1); if (tFreq.containsKey(leftChar) && windowFreq.get(leftChar).intValue() < tFreq.get(leftChar).intValue()) { formed--; } left++; } } return minLength == Integer.MAX_VALUE ? "" : s.substring(minStart, minStart + minLength); }}Complexity
Time
O(m + n)
Space
O(k)
Trade-offs
Pros
Optimal time complexity.
Efficiently solves the problem within the given constraints.
Cons
The logic can be tricky to implement correctly, with several conditions to track (window validity, character counts, pointer movements).
Solutions
Solution
public class Solution { public string MinWindow ( string s , string t ) { int [] need = new int [ 128 ]; int [] window = new int [ 128 ]; foreach ( var c in t ) { ++ need [ c ]; } int cnt = 0 , j = 0 , k = - 1 , mi = 1 << 30 ; for ( int i = 0 ; i < s . Length ; ++ i ) { ++ window [ s [ i ]]; if ( need [ s [ i ]] >= window [ s [ i ]]) { ++ cnt ; } while ( cnt == t . Length ) { if ( i - j + 1 < mi ) { mi = i - j + 1 ; k = j ; } if ( need [ s [ j ]] >= window [ s [ j ]]) { -- cnt ; } -- window [ s [ j ++]]; } } return k < 0 ? "" : s . Substring ( k , mi ); } }class Solution { public String minWindow ( String s , String t ) { int [] need = new int [ 128 ]; int [] window = new int [ 128 ]; int m = s . length (), n = t . length (); for ( int i = 0 ; i < n ; ++ i ) { ++ need [ t . charAt ( i )]; } int cnt = 0 , j = 0 , k = - 1 , mi = 1 << 30 ; for ( int i = 0 ; i < m ; ++ i ) { ++ window [ s . charAt ( i )]; if ( need [ s . charAt ( i )] >= window [ s . charAt ( i )]) { ++ cnt ; } while ( cnt == n ) { if ( i - j + 1 < mi ) { mi = i - j + 1 ; k = j ; } if ( need [ s . charAt ( j )] >= window [ s . charAt ( j )]) { -- cnt ; } -- window [ s . charAt ( j ++)]; } } return k < 0 ? "" : s . substring ( k , k + mi ); } }class Solution { public: string minWindow ( string s , string t ) { int need [ 128 ]{}; int window [ 128 ]{}; int m = s . size (), n = t . size (); for ( char & c : t ) { ++ need [ c ]; } int cnt = 0 , j = 0 , k = - 1 , mi = 1 << 30 ; for ( int i = 0 ; i < m ; ++ i ) { ++ window [ s [ i ]]; if ( need [ s [ i ]] >= window [ s [ i ]]) { ++ cnt ; } while ( cnt == n ) { if ( i - j + 1 < mi ) { mi = i - j + 1 ; k = j ; } if ( need [ s [ j ]] >= window [ s [ j ]]) { -- cnt ; } -- window [ s [ j ++ ]]; } } return k < 0 ? "" : s . substr ( k , mi ); } };''' >>> deq = collections.deque([]) >>> deq.append(11) >>> deq.append(22) >>> deq.append(33) >>> >>> deq[0] 11 ''' import collections class Solution : def minWindow ( self , s : str , t : str ) -> str : cnt = 0 need = collections . Counter ( t ) start , end = len ( s ), 3 * len ( s ) # Arbitrary 3, just make sure end-start is larger than input s length, for later min-check d = {} # Using queue to store indexes, good for a large amount of API calls deq = collections . deque ([]) for i , c in enumerate ( s ): if c in need : deq . append ( i ) d [ c ] = d . get ( c , 0 ) + 1 # '=' also +1, because it's inceased already one line above :) if d [ c ] <= need [ c ]: cnt += 1 while deq and d [ s [ deq [ 0 ]]] > need [ s [ deq [ 0 ]]]: d [ s [ deq . popleft ()]] -= 1 if cnt == len ( t ) and deq [ - 1 ] - deq [ 0 ] < end - start : start , end = deq [ 0 ], deq [ - 1 ] return s [ start : end + 1 ] ############ from collections import Counter class Solution : def minWindow ( self , s : str , t : str ) -> str : ans = '' m , n = len ( s ), len ( t ) if m < n : return ans need = Counter ( t ) window = Counter () i , cnt , mi = 0 , 0 , inf for j , c in enumerate ( s ): window [ c ] += 1 if need [ c ] >= window [ c ]: # >= , because 1 line above already +=1 cnt += 1 while cnt == n : if j - i + 1 < mi : # in while mi = j - i + 1 ans = s [ i : j + 1 ] c = s [ i ] if need [ c ] >= window [ c ]: # char in window but not in need, need[c]=0, window[c]=1..2.. cnt -= 1 window [ c ] -= 1 i += 1 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.