Bird
Raised Fist0
Interview Prepchallenge-problemshardAmazonFacebookGoogleMicrosoftLinkedIn

Minimum Window Substring

Choose your preparation mode4 modes available
🎯
Minimum Window Substring
hardMIXEDAmazonFacebookGoogle

Imagine you are searching through a large document to find the shortest snippet that contains all the keywords you need for your research.

💡 This problem requires finding a substring that contains all characters of another string, which is tricky because beginners often struggle with efficiently tracking character frequencies and managing dynamic window sizes.
📋
Problem Statement

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 there is no such substring, return the empty string "".

1 ≤ s.length, t.length ≤ 10^5s and t consist of English letters.
💡
Example
Input"s = \"ADOBECODEBANC\", t = \"ABC\""
Output"BANC"

The substring "BANC" contains all characters A, B, and C and is the shortest such substring.

  • t is longer than s → ""
  • s and t are identical → s
  • t contains characters not in s → ""
  • s or t is empty → ""
⚠️
Common Mistakes
Not checking if the window contains all required characters before shrinking

Returns incorrect or empty substring

✅ Use a 'have' counter to track when all required characters are included before shrinking

Using hash maps but forgetting to decrement counts when moving left pointer

Window counts become inaccurate, leading to incorrect results

✅ Always decrement the count of the character being removed from the window

Not handling characters in s that are not in t

Unnecessary shrinking or expansion, inefficient code

✅ Only update 'have' counter and check validity for characters present in t

Returning the substring using wrong indices after the loop

Off-by-one errors or empty string returned incorrectly

✅ Store start and end indices carefully and return substring accordingly

Using nested loops in brute force without pruning

Code times out on large inputs

✅ Explain brute force only conceptually and move to sliding window for coding

🧠
Brute Force (Nested Loops Checking All Substrings)
💡 Starting with brute force helps understand the problem deeply by checking every possible substring, even though it's inefficient.

Intuition

Check every substring of s and verify if it contains all characters of t by counting frequencies.

Algorithm

  1. Generate all possible substrings of s.
  2. For each substring, count the frequency of characters.
  3. Check if the substring contains all characters of t with required frequency.
  4. Keep track of the minimum length substring that satisfies the condition.
💡 The nested loops and frequency checks make this approach easy to understand but hard to optimize without further insight.
</>
Code
def min_window_brute_force(s: str, t: str) -> str:
    from collections import Counter
    if not s or not t:
        return ""
    t_count = Counter(t)
    required = len(t_count)
    min_len = float('inf')
    min_window = ""
    for i in range(len(s)):
        for j in range(i, len(s)):
            window = s[i:j+1]
            window_count = Counter(window)
            if all(window_count[c] >= t_count[c] for c in t_count):
                if j - i + 1 < min_len:
                    min_len = j - i + 1
                    min_window = window
    return min_window

# Example usage:
if __name__ == "__main__":
    print(min_window_brute_force("ADOBECODEBANC", "ABC"))  # Output: BANC
Line Notes
from collections import CounterImport Counter to easily count character frequencies, which simplifies frequency comparisons.
if not s or not t:Handle edge cases where input strings are empty to avoid unnecessary computation.
for i in range(len(s))Outer loop selects the start index of the substring to consider.
for j in range(i, len(s))Inner loop selects the end index of the substring, generating all substrings.
window_count = Counter(window)Count characters in the current substring to compare with required frequencies.
if all(window_count[c] >= t_count[c] for c in t_count)Check if current substring contains all required characters with correct frequency.
if j - i + 1 < min_lenUpdate minimum window if current substring is smaller than previously found ones.
import java.util.*;
public class MinWindowSubstringBrute {
    public static String minWindow(String s, String t) {
        if (s == null || t == null || s.length() == 0 || t.length() == 0) return "";
        Map<Character, Integer> tCount = new HashMap<>();
        for (char c : t.toCharArray()) tCount.put(c, tCount.getOrDefault(c, 0) + 1);
        int minLen = Integer.MAX_VALUE;
        String minWindow = "";
        for (int i = 0; i < s.length(); i++) {
            for (int j = i; j < s.length(); j++) {
                String window = s.substring(i, j + 1);
                Map<Character, Integer> windowCount = new HashMap<>();
                for (char c : window.toCharArray()) windowCount.put(c, windowCount.getOrDefault(c, 0) + 1);
                boolean valid = true;
                for (char c : tCount.keySet()) {
                    if (windowCount.getOrDefault(c, 0) < tCount.get(c)) {
                        valid = false;
                        break;
                    }
                }
                if (valid && window.length() < minLen) {
                    minLen = window.length();
                    minWindow = window;
                }
            }
        }
        return minWindow;
    }
    public static void main(String[] args) {
        System.out.println(minWindow("ADOBECODEBANC", "ABC")); // Output: BANC
    }
}
Line Notes
if (s == null || t == null || s.length() == 0 || t.length() == 0)Check for empty or null inputs early to avoid unnecessary processing.
for (char c : t.toCharArray())Count frequency of characters in t to know requirements.
for (int i = 0; i < s.length(); i++)Outer loop selects start index of substring.
for (int j = i; j < s.length(); j++)Inner loop selects end index of substring.
Map<Character, Integer> windowCount = new HashMap<>();Count characters in current substring for frequency comparison.
if (windowCount.getOrDefault(c, 0) < tCount.get(c))Check if current substring lacks required characters.
if (valid && window.length() < minLen)Update minimum window if current substring is valid and smaller.
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;

string minWindowBrute(string s, string t) {
    if (s.empty() || t.empty()) return "";
    unordered_map<char, int> tCount;
    for (char c : t) tCount[c]++;
    int minLen = INT_MAX;
    string minWindow = "";
    for (int i = 0; i < (int)s.size(); i++) {
        for (int j = i; j < (int)s.size(); j++) {
            unordered_map<char, int> windowCount;
            for (int k = i; k <= j; k++) windowCount[s[k]]++;
            bool valid = true;
            for (auto &p : tCount) {
                if (windowCount[p.first] < p.second) {
                    valid = false;
                    break;
                }
            }
            if (valid && j - i + 1 < minLen) {
                minLen = j - i + 1;
                minWindow = s.substr(i, minLen);
            }
        }
    }
    return minWindow;
}

int main() {
    cout << minWindowBrute("ADOBECODEBANC", "ABC") << endl; // Output: BANC
    return 0;
}
Line Notes
if (s.empty() || t.empty()) return "";Handle empty input strings immediately to avoid unnecessary computation.
for (char c : t) tCount[c]++;Count frequency of characters in t to know requirements.
for (int i = 0; i < (int)s.size(); i++)Outer loop selects start index of substring.
for (int j = i; j < (int)s.size(); j++)Inner loop selects end index of substring.
for (int k = i; k <= j; k++) windowCount[s[k]]++;Count characters in current substring for frequency comparison.
if (windowCount[p.first] < p.second)Check if current substring lacks required characters.
if (valid && j - i + 1 < minLen)Update minimum window if current substring is valid and smaller.
function minWindowBruteForce(s, t) {
    if (!s || !t) return "";
    const tCount = {};
    for (const c of t) tCount[c] = (tCount[c] || 0) + 1;
    let minLen = Infinity;
    let minWindow = "";
    for (let i = 0; i < s.length; i++) {
        for (let j = i; j < s.length; j++) {
            const window = s.substring(i, j + 1);
            const windowCount = {};
            for (const c of window) windowCount[c] = (windowCount[c] || 0) + 1;
            let valid = true;
            for (const c in tCount) {
                if (!windowCount[c] || windowCount[c] < tCount[c]) {
                    valid = false;
                    break;
                }
            }
            if (valid && window.length < minLen) {
                minLen = window.length;
                minWindow = window;
            }
        }
    }
    return minWindow;
}

// Example usage:
console.log(minWindowBruteForce("ADOBECODEBANC", "ABC")); // Output: BANC
Line Notes
if (!s || !t) return "";Return empty string if inputs are empty or null to handle edge cases.
for (const c of t) tCount[c] = (tCount[c] || 0) + 1;Count frequency of characters in t to know requirements.
for (let i = 0; i < s.length; i++)Outer loop selects start index of substring.
for (let j = i; j < s.length; j++)Inner loop selects end index of substring.
for (const c of window) windowCount[c] = (windowCount[c] || 0) + 1;Count characters in current substring for frequency comparison.
if (!windowCount[c] || windowCount[c] < tCount[c])Check if current substring lacks required characters.
if (valid && window.length < minLen)Update minimum window if current substring is valid and smaller.
⏱
Complexity
TimeO(n^3)
SpaceO(n)

There are O(n^2) substrings and counting frequencies for each substring takes O(n), resulting in O(n^3) time.

💡 For n=20, this means roughly 8000 operations, which is slow but manageable; for n=1000, it becomes impractical.
Interview Verdict: TLE

This approach is too slow for large inputs but is useful to understand the problem and correctness before optimizing.

🧠
Sliding Window with Frequency Counting
💡 This approach introduces the sliding window technique to reduce redundant checks and improve efficiency by dynamically adjusting the window size.

Intuition

Expand the right pointer to include characters until all required are found, then move the left pointer to minimize the window while maintaining validity.

Algorithm

  1. Count the frequency of characters in t.
  2. Use two pointers (left and right) to represent the current window in s.
  3. Expand right pointer to include characters until the window contains all characters of t.
  4. Shrink left pointer to remove unnecessary characters and update minimum window.
  5. Repeat until right pointer reaches the end of s.
💡 The challenge is correctly tracking when the window is valid and efficiently updating counts while moving pointers.
</>
Code
def min_window_sliding_window(s: str, t: str) -> str:
    from collections import Counter
    if not s or not t:
        return ""
    t_count = Counter(t)
    window_count = {}
    have, need = 0, len(t_count)
    res, res_len = [-1, -1], float('inf')
    left = 0
    for right, c in enumerate(s):
        window_count[c] = window_count.get(c, 0) + 1
        if c in t_count and window_count[c] == t_count[c]:
            have += 1
        while have == need:
            if (right - left + 1) < res_len:
                res = [left, right]
                res_len = right - left + 1
            window_count[s[left]] -= 1
            if s[left] in t_count and window_count[s[left]] < t_count[s[left]]:
                have -= 1
            left += 1
    left, right = res
    return s[left:right+1] if res_len != float('inf') else ""

# Example usage:
if __name__ == "__main__":
    print(min_window_sliding_window("ADOBECODEBANC", "ABC"))  # Output: BANC
Line Notes
from collections import CounterUse Counter to count characters in t efficiently, simplifying frequency tracking.
t_count = Counter(t)Store required character frequencies to know what to match.
window_count = {}Track frequencies of characters in the current window dynamically.
have, need = 0, len(t_count)Track how many unique required characters are currently satisfied.
for right, c in enumerate(s)Expand window by moving right pointer through s.
if c in t_count and window_count[c] == t_count[c]Increment 'have' when a required character's frequency matches the requirement.
while have == needTry to shrink the window from the left while it still contains all required characters.
window_count[s[left]] -= 1Remove the leftmost character from the window count as we shrink.
if s[left] in t_count and window_count[s[left]] < t_count[s[left]]Decrement 'have' if the window no longer satisfies the frequency requirement for a character.
import java.util.*;
public class MinWindowSubstringSliding {
    public static String minWindow(String s, String t) {
        if (s == null || t == null || s.length() == 0 || t.length() == 0) return "";
        Map<Character, Integer> tCount = new HashMap<>();
        for (char c : t.toCharArray()) tCount.put(c, tCount.getOrDefault(c, 0) + 1);
        Map<Character, Integer> windowCount = new HashMap<>();
        int have = 0, need = tCount.size();
        int left = 0, minLen = Integer.MAX_VALUE;
        int[] res = {-1, -1};
        for (int right = 0; right < s.length(); right++) {
            char c = s.charAt(right);
            windowCount.put(c, windowCount.getOrDefault(c, 0) + 1);
            if (tCount.containsKey(c) && windowCount.get(c).intValue() == tCount.get(c).intValue()) {
                have++;
            }
            while (have == need) {
                if (right - left + 1 < minLen) {
                    minLen = right - left + 1;
                    res[0] = left;
                    res[1] = right;
                }
                char leftChar = s.charAt(left);
                windowCount.put(leftChar, windowCount.get(leftChar) - 1);
                if (tCount.containsKey(leftChar) && windowCount.get(leftChar) < tCount.get(leftChar)) {
                    have--;
                }
                left++;
            }
        }
        return res[0] == -1 ? "" : s.substring(res[0], res[1] + 1);
    }
    public static void main(String[] args) {
        System.out.println(minWindow("ADOBECODEBANC", "ABC")); // Output: BANC
    }
}
Line Notes
Map<Character, Integer> tCount = new HashMap<>();Count required characters in t to know frequency requirements.
Map<Character, Integer> windowCount = new HashMap<>();Track characters in the current window dynamically.
int have = 0, need = tCount.size();Track how many unique required characters are currently satisfied.
for (int right = 0; right < s.length(); right++)Expand window by moving right pointer through s.
if (tCount.containsKey(c) && windowCount.get(c).intValue() == tCount.get(c).intValue())Increment 'have' when a required character's frequency matches the requirement.
while (have == need)Shrink window from left while it still contains all required characters.
windowCount.put(leftChar, windowCount.get(leftChar) - 1);Remove left character from window count as window shrinks.
if (tCount.containsKey(leftChar) && windowCount.get(leftChar) < tCount.get(leftChar))Decrement 'have' if window no longer satisfies frequency requirement.
#include <iostream>
#include <string>
#include <unordered_map>
#include <climits>
using namespace std;

string minWindowSliding(string s, string t) {
    if (s.empty() || t.empty()) return "";
    unordered_map<char, int> tCount;
    for (char c : t) tCount[c]++;
    unordered_map<char, int> windowCount;
    int have = 0, need = (int)tCount.size();
    int left = 0, minLen = INT_MAX;
    pair<int, int> res = {-1, -1};
    for (int right = 0; right < (int)s.size(); right++) {
        char c = s[right];
        windowCount[c]++;
        if (tCount.count(c) && windowCount[c] == tCount[c]) {
            have++;
        }
        while (have == need) {
            if (right - left + 1 < minLen) {
                minLen = right - left + 1;
                res = {left, right};
            }
            char leftChar = s[left];
            windowCount[leftChar]--;
            if (tCount.count(leftChar) && windowCount[leftChar] < tCount[leftChar]) {
                have--;
            }
            left++;
        }
    }
    if (res.first == -1) return "";
    return s.substr(res.first, minLen);
}

int main() {
    cout << minWindowSliding("ADOBECODEBANC", "ABC") << endl; // Output: BANC
    return 0;
}
Line Notes
unordered_map<char, int> tCount;Count required characters in t to know frequency requirements.
unordered_map<char, int> windowCount;Track characters in the current window dynamically.
int have = 0, need = (int)tCount.size();Track how many unique required characters are currently satisfied.
for (int right = 0; right < (int)s.size(); right++)Expand window by moving right pointer through s.
if (tCount.count(c) && windowCount[c] == tCount[c])Increment 'have' when a required character's frequency matches the requirement.
while (have == need)Shrink window from left while it still contains all required characters.
windowCount[leftChar]--;Remove left character from window count as window shrinks.
if (tCount.count(leftChar) && windowCount[leftChar] < tCount[leftChar])Decrement 'have' if window no longer satisfies frequency requirement.
function minWindowSlidingWindow(s, t) {
    if (!s || !t) return "";
    const tCount = {};
    for (const c of t) tCount[c] = (tCount[c] || 0) + 1;
    const windowCount = {};
    let have = 0, need = Object.keys(tCount).length;
    let left = 0, minLen = Infinity;
    let res = [-1, -1];
    for (let right = 0; right < s.length; right++) {
        const c = s[right];
        windowCount[c] = (windowCount[c] || 0) + 1;
        if (tCount[c] && windowCount[c] === tCount[c]) {
            have++;
        }
        while (have === need) {
            if (right - left + 1 < minLen) {
                minLen = right - left + 1;
                res = [left, right];
            }
            const leftChar = s[left];
            windowCount[leftChar]--;
            if (tCount[leftChar] && windowCount[leftChar] < tCount[leftChar]) {
                have--;
            }
            left++;
        }
    }
    return res[0] === -1 ? "" : s.substring(res[0], res[1] + 1);
}

// Example usage:
console.log(minWindowSlidingWindow("ADOBECODEBANC", "ABC")); // Output: BANC
Line Notes
const tCount = {};Count required characters in t to know frequency requirements.
const windowCount = {};Track characters in the current window dynamically.
let have = 0, need = Object.keys(tCount).length;Track how many unique required characters are currently satisfied.
for (let right = 0; right < s.length; right++)Expand window by moving right pointer through s.
if (tCount[c] && windowCount[c] === tCount[c])Increment 'have' when a required character's frequency matches the requirement.
while (have === need)Shrink window from left while it still contains all required characters.
windowCount[leftChar]--;Remove left character from window count as window shrinks.
if (tCount[leftChar] && windowCount[leftChar] < tCount[leftChar])Decrement 'have' if window no longer satisfies frequency requirement.
⏱
Complexity
TimeO(n)
SpaceO(m)

Each character is visited at most twice by left and right pointers, and frequency maps use O(m) space where m is size of t.

💡 For n=10^5, this approach is efficient enough to run within time limits.
Interview Verdict: Accepted

This approach is optimal for this problem and is what interviewers expect for an efficient solution.

🧠
Optimized Sliding Window with Fixed-Size Frequency Arrays
💡 Using fixed-size arrays instead of hash maps can speed up frequency tracking when input is limited to ASCII letters.

Intuition

Replace hash maps with arrays indexed by character codes to reduce overhead and improve runtime.

Algorithm

  1. Initialize frequency arrays for t and the current window.
  2. Use two pointers to expand and shrink the window as in the sliding window approach.
  3. Update counts using array indices instead of hash maps.
  4. Track when the window satisfies all character requirements.
  5. Return the minimum window substring found.
💡 The logic is the same as the sliding window approach but uses arrays for faster frequency updates.
</>
Code
def min_window_optimized(s: str, t: str) -> str:
    if not s or not t:
        return ""
    from collections import defaultdict
    t_count = [0] * 128
    window_count = [0] * 128
    for c in t:
        t_count[ord(c)] += 1
    have, need = 0, sum(1 for x in t_count if x > 0)
    res, res_len = [-1, -1], float('inf')
    left = 0
    for right in range(len(s)):
        c = s[right]
        window_count[ord(c)] += 1
        if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
            have += 1
        while have == need:
            if (right - left + 1) < res_len:
                res = [left, right]
                res_len = right - left + 1
            window_count[ord(s[left])] -= 1
            if t_count[ord(s[left])] > 0 and window_count[ord(s[left])] < t_count[ord(s[left])]:
                have -= 1
            left += 1
    left, right = res
    return s[left:right+1] if res_len != float('inf') else ""

# Example usage:
if __name__ == "__main__":
    print(min_window_optimized("ADOBECODEBANC", "ABC"))  # Output: BANC
Line Notes
t_count = [0] * 128Initialize fixed-size array for ASCII character counts to enable O(1) access.
window_count = [0] * 128Track frequencies in current window using array for faster updates.
for c in t: t_count[ord(c)] += 1Count characters in t by ASCII index for direct access.
have, need = 0, sum(1 for x in t_count if x > 0)Count how many unique characters are needed to satisfy the window.
window_count[ord(c)] += 1Increment window count for current character efficiently.
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]Check if requirement for character met to update 'have'.
while have == needShrink window while it remains valid to find minimum substring.
window_count[ord(s[left])] -= 1Remove left character from window count as window shrinks.
public class MinWindowSubstringOptimized {
    public static String minWindow(String s, String t) {
        if (s == null || t == null || s.length() == 0 || t.length() == 0) return "";
        int[] tCount = new int[128];
        int[] windowCount = new int[128];
        for (char c : t.toCharArray()) tCount[c]++;
        int have = 0, need = 0;
        for (int count : tCount) if (count > 0) need++;
        int left = 0, minLen = Integer.MAX_VALUE;
        int[] res = {-1, -1};
        for (int right = 0; right < s.length(); right++) {
            char c = s.charAt(right);
            windowCount[c]++;
            if (tCount[c] > 0 && windowCount[c] == tCount[c]) have++;
            while (have == need) {
                if (right - left + 1 < minLen) {
                    minLen = right - left + 1;
                    res[0] = left;
                    res[1] = right;
                }
                char leftChar = s.charAt(left);
                windowCount[leftChar]--;
                if (tCount[leftChar] > 0 && windowCount[leftChar] < tCount[leftChar]) have--;
                left++;
            }
        }
        return res[0] == -1 ? "" : s.substring(res[0], res[1] + 1);
    }
    public static void main(String[] args) {
        System.out.println(minWindow("ADOBECODEBANC", "ABC")); // Output: BANC
    }
}
Line Notes
int[] tCount = new int[128];Use fixed-size array for ASCII counts to enable constant-time access.
int[] windowCount = new int[128];Track window frequencies with array for efficient updates.
for (char c : t.toCharArray()) tCount[c]++;Count characters in t by ASCII index.
int have = 0, need = 0; for (int count : tCount) if (count > 0) need++;Count unique characters needed to satisfy window.
windowCount[c]++;Increment window count for current character.
if (tCount[c] > 0 && windowCount[c] == tCount[c]) have++;Increment 'have' when requirement met for a character.
while (have == need)Shrink window while it remains valid to find minimum substring.
windowCount[leftChar]--;Remove left character from window count as window shrinks.
#include <iostream>
#include <string>
#include <climits>
using namespace std;

string minWindowOptimized(string s, string t) {
    if (s.empty() || t.empty()) return "";
    int tCount[128] = {0};
    int windowCount[128] = {0};
    for (char c : t) tCount[c]++;
    int have = 0, need = 0;
    for (int i = 0; i < 128; i++) if (tCount[i] > 0) need++;
    int left = 0, minLen = INT_MAX;
    pair<int, int> res = {-1, -1};
    for (int right = 0; right < (int)s.size(); right++) {
        char c = s[right];
        windowCount[c]++;
        if (tCount[c] > 0 && windowCount[c] == tCount[c]) have++;
        while (have == need) {
            if (right - left + 1 < minLen) {
                minLen = right - left + 1;
                res = {left, right};
            }
            char leftChar = s[left];
            windowCount[leftChar]--;
            if (tCount[leftChar] > 0 && windowCount[leftChar] < tCount[leftChar]) have--;
            left++;
        }
    }
    if (res.first == -1) return "";
    return s.substr(res.first, minLen);
}

int main() {
    cout << minWindowOptimized("ADOBECODEBANC", "ABC") << endl; // Output: BANC
    return 0;
}
Line Notes
int tCount[128] = {0};Initialize fixed-size array for ASCII counts for constant-time access.
int windowCount[128] = {0};Track window frequencies with array for efficient updates.
for (char c : t) tCount[c]++;Count characters in t by ASCII index.
int have = 0, need = 0; for (int i = 0; i < 128; i++) if (tCount[i] > 0) need++;Count unique characters needed to satisfy window.
windowCount[c]++;Increment window count for current character.
if (tCount[c] > 0 && windowCount[c] == tCount[c]) have++;Increment 'have' when requirement met for a character.
while (have == need)Shrink window while it remains valid to find minimum substring.
windowCount[leftChar]--;Remove left character from window count as window shrinks.
function minWindowOptimized(s, t) {
    if (!s || !t) return "";
    const tCount = new Array(128).fill(0);
    const windowCount = new Array(128).fill(0);
    for (const c of t) tCount[c.charCodeAt(0)]++;
    let have = 0, need = tCount.filter(x => x > 0).length;
    let left = 0, minLen = Infinity;
    let res = [-1, -1];
    for (let right = 0; right < s.length; right++) {
        const c = s.charCodeAt(right);
        windowCount[c]++;
        if (tCount[c] > 0 && windowCount[c] === tCount[c]) have++;
        while (have === need) {
            if (right - left + 1 < minLen) {
                minLen = right - left + 1;
                res = [left, right];
            }
            const leftChar = s.charCodeAt(left);
            windowCount[leftChar]--;
            if (tCount[leftChar] > 0 && windowCount[leftChar] < tCount[leftChar]) have--;
            left++;
        }
    }
    return res[0] === -1 ? "" : s.substring(res[0], res[1] + 1);
}

// Example usage:
console.log(minWindowOptimized("ADOBECODEBANC", "ABC")); // Output: BANC
Line Notes
const tCount = new Array(128).fill(0);Initialize fixed-size array for ASCII counts for constant-time access.
const windowCount = new Array(128).fill(0);Track window frequencies with array for efficient updates.
for (const c of t) tCount[c.charCodeAt(0)]++;Count characters in t by ASCII index.
let have = 0, need = tCount.filter(x => x > 0).length;Count unique characters needed to satisfy window.
windowCount[c]++;Increment window count for current character.
if (tCount[c] > 0 && windowCount[c] === tCount[c]) have++;Increment 'have' when requirement met for a character.
while (have === need)Shrink window while it remains valid to find minimum substring.
windowCount[leftChar]--;Remove left character from window count as window shrinks.
⏱
Complexity
TimeO(n)
SpaceO(1)

Frequency arrays use constant space (128 ASCII chars), and two pointers traverse s once.

💡 This approach is the fastest in practice due to constant-time frequency lookups.
Interview Verdict: Accepted

This is a micro-optimization over the standard sliding window and is ideal when input is ASCII.

📊
All Approaches - One-Glance Tradeoffs
💡 In interviews, coding the sliding window approach (Approach 2) is usually best; brute force is for explanation only.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(n^3)O(n)NoN/AMention only - never code
2. Sliding Window with Hash MapsO(n)O(m)NoYesCode this approach in most interviews
3. Optimized Sliding Window with ArraysO(n)O(1)NoYesMention as optimization if input is ASCII
💼
Interview Strategy
💡 Use this guide to understand the problem deeply, practice coding all approaches, and prepare to explain tradeoffs clearly in interviews.

How to Present

Clarify the problem and constraints with the interviewer.Explain the brute force approach to show understanding of the problem.Introduce the sliding window technique as an optimization.Discuss further optimizations like fixed-size arrays if applicable.Write clean, tested code and explain your logic clearly.

Time Allocation

Clarify: 2min → Approach: 3min → Code: 10min → Test: 5min. Total ~20min

What the Interviewer Tests

The interviewer tests your ability to identify the sliding window pattern, manage frequency counts correctly, and optimize for time and space.

Common Follow-ups

  • What if t contains unicode characters? → Use hash maps instead of fixed arrays.
  • How to handle case-insensitive matching? → Normalize strings before processing.
💡 These follow-ups test your ability to adapt the solution to real-world variations and constraints.
🔍
Pattern Recognition

When to Use

1) Need to find substring containing all characters of another string, 2) Order of characters does not matter, 3) Need minimum or maximum length substring, 4) Characters have frequency requirements.

Signature Phrases

minimum window substringcontains all charactersfrequency of characters

NOT This Pattern When

Problems requiring subsequence matching or order-sensitive substring matching are different patterns.

Similar Problems

Substring with Concatenation of All Words - similar frequency and substring matchingLongest Substring Without Repeating Characters - sliding window patternFind All Anagrams in a String - frequency tracking with sliding window

Practice

(1/5)
1. Given an integer array and an integer k, you need to count the number of contiguous subarrays that contain exactly k odd numbers. Which algorithmic approach guarantees an optimal solution with linear time complexity?
easy
A. Brute force nested loops checking all subarrays and counting odd numbers
B. Dynamic programming with state representing counts of odd numbers in subarrays
C. Sliding window with two pointers maintaining at most k odd numbers and using inclusion-exclusion
D. Greedy approach expanding subarrays until k odd numbers are found, then restarting

Solution

  1. Step 1: Understand problem constraints

    The problem requires counting subarrays with exactly k odd numbers efficiently.
  2. Step 2: Identify approach with linear time

    Sliding window with two pointers can count subarrays with at most k odd numbers in O(n), then use inclusion-exclusion to get exactly k.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Sliding window with two pointers is known O(n) solution [OK]
Hint: Sliding window with two pointers solves this in O(n) [OK]
Common Mistakes:
  • Thinking brute force is optimal
  • Confusing DP with sliding window
  • Using greedy that fails on overlapping subarrays
2. Given the following code for maxSatisfied, what is the value of max_extra after the first for-loop that slides the window (before the final return) when called with customers = [1, 0, 1, 2], grumpy = [1, 0, 1, 1], and X = 2?
def maxSatisfied(customers, grumpy, X):
    n = len(customers)
    base_satisfied = 0
    for i in range(n):
        if grumpy[i] == 0:
            base_satisfied += customers[i]
    window_sum = 0
    for i in range(X):
        if grumpy[i] == 1:
            window_sum += customers[i]
    max_extra = window_sum
    for i in range(X, n):
        if grumpy[i] == 1:
            window_sum += customers[i]
        if grumpy[i - X] == 1:
            window_sum -= customers[i - X]
        if window_sum > max_extra:
            max_extra = window_sum
    return base_satisfied + max_extra
easy
A. 2
B. 3
C. 4
D. 1

Solution

  1. Step 1: Calculate base_satisfied

    grumpy = [1,0,1,1], so only index 1 is not grumpy -> base_satisfied = customers[1] = 0
  2. Step 2: Initialize window_sum for first X=2 minutes

    Indices 0 and 1: grumpy[0]=1 -> add customers[0]=1; grumpy[1]=0 -> skip; window_sum=1; max_extra=1
  3. Step 3: Slide window from i=2 to 3

    i=2: grumpy[2]=1 -> add customers[2]=1 -> window_sum=2; remove grumpy[0]=1 -> subtract customers[0]=1 -> window_sum=1; max_extra=1 (no update)
    i=3: grumpy[3]=1 -> add customers[3]=2 -> window_sum=3; remove grumpy[1]=0 -> no subtraction; max_extra updated to 3
  4. Final Answer:

    Option B -> Option B
  5. Quick Check:

    max_extra after sliding window is 3 [OK]
Hint: Track window_sum updates carefully [OK]
Common Mistakes:
  • Off-by-one in sliding window indices
  • Forgetting to subtract outgoing element
  • Miscounting base satisfaction
3. Consider the following Python code implementing the sliding window maximum using a monotonic deque. What is the returned result when calling maxSlidingWindow([2, 1, 3], 2)?
easy
A. [2, 3]
B. [2, 1, 3]
C. [3]
D. [1, 3]

Solution

  1. Step 1: Trace the deque and result for each iteration

    i=0: dq=[0], result=[]; i=1: nums[1]=1 < nums[0]=2, dq=[0,1], result=[]; i=2: nums[2]=3 > nums[1]=1 pop 1, nums[2]=3 > nums[0]=2 pop 0, dq=[2]; after i=1 (window [2,1]) result append nums[0]=2; after i=2 (window [1,3]) result append nums[2]=3.
  2. Step 2: Collect final result

    Result after full iteration is [2, 3].
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Output matches expected sliding window maximum for windows [2,1] and [1,3] [OK]
Hint: Deque stores indices of max candidates, popping smaller elements [OK]
Common Mistakes:
  • Appending result before window is full
  • Not removing indices outside the window
  • Confusing values and indices in deque
4. The following code attempts to solve the Longest Repeating Character Replacement problem. Which line contains a subtle bug that can cause incorrect results on some inputs?
medium
A. Line incrementing left pointer inside the while loop
B. Line incrementing freq for s[right]
C. Line updating max_count inside the while loop after shrinking the window
D. Line updating max_len after the while loop

Solution

  1. Step 1: Understand max_count usage

    max_count should track the max frequency character in the current window efficiently without recomputing entire freq array.
  2. Step 2: Identify bug in max_count update

    Updating max_count by max(freq) inside the while loop is costly and can cause stale or incorrect max_count, leading to wrong window shrinking decisions.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    max_count must not be recomputed inside the shrinking loop [OK]
Hint: max_count updated only on window expansion, not shrinking [OK]
Common Mistakes:
  • Recomputing max_count on every shrink
  • Not updating frequency counts correctly
  • Shrinking window too early or late
5. Suppose the problem is modified so you can pick cards from the start or end any number of times, including reusing cards multiple times (infinite picks allowed). Which approach correctly adapts to this variant?
hard
A. Use a dynamic programming approach to find the maximum sum of k picks allowing repeated cards from ends.
B. Use prefix sums only on the first k cards from the start, ignoring the end cards.
C. Use a greedy approach that always picks the card with the highest point value from either end until k picks are made.
D. Use the original sliding window approach on the middle subarray since it still finds the minimal subarray to exclude.

Solution

  1. Step 1: Understand the new constraint

    Reusing cards means picks are not limited to unique cards; the problem becomes unbounded with repetition allowed.
  2. Step 2: Recognize sliding window limitation

    The sliding window approach assumes each card can be picked once; it fails with infinite reuse.
  3. Step 3: Use dynamic programming

    DP can model picking cards with repetition from ends, tracking maximum sums for each number of picks.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    DP handles repeated picks; sliding window does not [OK]
Hint: Reusing cards breaks sliding window; DP needed [OK]
Common Mistakes:
  • Assuming sliding window still works with repeated picks
  • Greedy fails to consider future picks
  • Ignoring picks from the end
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard