title: Minimum Window Substring
slug: minimum-window-substring
difficulty: hard
leetcode_id: 76
leetcode_url: https://leetcode.com/problems/minimum-window-substring/
categories:
  - strings
  - hash-tables
patterns:
  - slug: sliding-window
    is_optimal: true

function_signature: "def min_window(s: str, t: str) -> str:"

test_cases:
  visible:
    - input: { s: "ADOBECODEBANC", t: "ABC" }
      expected: "BANC"
    - input: { s: "a", t: "a" }
      expected: "a"
    - input: { s: "a", t: "aa" }
      expected: ""
  hidden:
    - input: { s: "ab", t: "a" }
      expected: "a"
    - input: { s: "ab", t: "b" }
      expected: "b"
    - input: { s: "bba", t: "ab" }
      expected: "ba"
    - input: { s: "abc", t: "cba" }
      expected: "abc"

description: |
  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**.

constraints: |
  - `m == s.length`
  - `n == t.length`
  - `1 <= m, n <= 10^5`
  - `s` and `t` consist of uppercase and lowercase English letters

examples:
  - input: 's = "ADOBECODEBANC", t = "ABC"'
    output: '"BANC"'
    explanation: "The minimum window substring \"BANC\" includes 'A', 'B', and 'C' from string t."
  - input: 's = "a", t = "a"'
    output: '"a"'
    explanation: "The entire string s is the minimum window."
  - 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."

explanation:
  intuition: |
    Imagine you're looking through a telescope at a long banner of letters (string `s`), and you need to find the smallest section that contains all the letters from your shopping list (string `t`).

    The key insight is that we don't need to check every possible substring — that would be far too slow. Instead, we can use a **sliding window** technique: we expand a window to the right until it contains all required characters, then shrink it from the left to find the minimum valid window.

    Think of it like this: you're stretching a rubber band around the letters. First, you stretch it wide enough to capture everything you need. Then, you carefully release from the left side, making it as small as possible while still holding all required letters.

    The critical realisation is that we need to track **character frequencies**, not just presence. If `t = "aa"`, we need at least two `'a'`s in our window. We use two hash maps: one to count what we need, and one to count what we have in our current window.

  approach: |
    We solve this using the **Sliding Window with Two Pointers** technique:

    **Step 1: Build a frequency map for target string `t`**

    - Create a hash map `need` counting each character's frequency in `t`
    - Track `required`: the number of unique characters we must match (size of `need`)

    &nbsp;

    **Step 2: Initialise window tracking variables**

    - `left`, `right`: Window boundaries, both start at `0`
    - `have`: Count of unique characters that meet the required frequency (starts at `0`)
    - `window`: Hash map tracking character counts in our current window
    - `result`: Tuple of `(window_length, left_index, right_index)` for the answer

    &nbsp;

    **Step 3: Expand the window by moving `right`**

    - Add `s[right]` to the `window` map
    - If this character is in `need` and its count now matches the required count, increment `have`
    - Continue expanding until `have == required` (window contains all needed characters)

    &nbsp;

    **Step 4: Contract the window by moving `left`**

    - While the window is valid (`have == required`):
      - Update `result` if this window is smaller than our best
      - Remove `s[left]` from the window map
      - If this causes a needed character to drop below its required count, decrement `have`
      - Move `left` forward

    &nbsp;

    **Step 5: Return the result**

    - If we never found a valid window, return `""`
    - Otherwise, return the substring from `result`

  common_pitfalls:
    - title: The Brute Force Trap
      description: |
        A naive approach checks every possible substring of `s` to see if it contains all characters of `t`. With `m` substrings of varying lengths and `O(m + n)` to validate each, this results in **O(m^2 * n)** time complexity.

        For `m = n = 10^5`, this means up to 10^15 operations — guaranteed **Time Limit Exceeded**.
      wrong_approach: "Nested loops checking all substrings"
      correct_approach: "Sliding window expanding and contracting in O(m + n)"

    - title: Checking Only Character Presence
      description: |
        It's tempting to just check if all characters from `t` exist in the window. But `t` can have **duplicate characters**.

        For `t = "aa"`, a window containing just one `'a'` is invalid. You must track **frequencies**, not just presence.
      wrong_approach: "Using a set to check character existence"
      correct_approach: "Using hash maps to track character counts"

    - title: Forgetting to Shrink the Window
      description: |
        Some solutions only expand the window when a character is missing, but forget to shrink it after finding a valid window.

        The key optimisation is that after finding any valid window, we must try to shrink it from the left. The first valid window is rarely the minimum — you need to keep contracting.
      wrong_approach: "Only expanding, never contracting"
      correct_approach: "Contract from left whenever window is valid"

    - title: Off-by-One Errors in Substring Extraction
      description: |
        When tracking window boundaries and extracting the final substring, it's easy to be off by one.

        Remember: `s[left:right+1]` includes both endpoints if using zero-indexed positions where `right` points to the last character in the window.

  key_takeaways:
    - "**Sliding Window Pattern**: When finding minimum/maximum substrings satisfying a condition, think expand-then-contract"
    - "**Two Hash Maps**: One for 'what we need', one for 'what we have' — a common technique for substring problems with character requirements"
    - "**Track Satisfied Conditions**: Instead of re-validating the entire window, track how many conditions are met and update incrementally"
    - "**Foundation for Similar Problems**: This technique applies to problems like Longest Substring Without Repeating Characters, Permutation in String, and Find All Anagrams"

  time_complexity: "O(m + n). We visit each character in `s` at most twice (once when expanding `right`, once when contracting `left`), and we do O(n) work to build the frequency map for `t`."
  space_complexity: "O(m + n). In the worst case, our hash maps store all unique characters from both strings. With only English letters, this is bounded by O(52) = O(1), but the general case is O(m + n)."

solutions:
  - approach_name: Sliding Window with Hash Maps
    is_optimal: true
    code: |
      from collections import Counter

      def min_window(s: str, t: str) -> str:
          if not t or not s:
              return ""

          # Count characters we need from t
          need = Counter(t)
          required = len(need)  # Number of unique chars to match

          # Window tracking
          window = {}
          have = 0  # How many unique chars meet required frequency

          # Result: (window_length, left, right)
          result = (float("inf"), 0, 0)

          left = 0
          for right in range(len(s)):
              # Expand: add character at right to window
              char = s[right]
              window[char] = window.get(char, 0) + 1

              # Check if this char's frequency now matches what we need
              if char in need and window[char] == need[char]:
                  have += 1

              # Contract: shrink window while it's valid
              while have == required:
                  # Update result if this window is smaller
                  window_len = right - left + 1
                  if window_len < result[0]:
                      result = (window_len, left, right)

                  # Remove leftmost character
                  left_char = s[left]
                  window[left_char] -= 1

                  # If this breaks a requirement, decrement have
                  if left_char in need and window[left_char] < need[left_char]:
                      have -= 1

                  left += 1

          # Return result
          length, start, end = result
          return s[start:end + 1] if length != float("inf") else ""
    explanation: |
      **Time Complexity:** O(m + n) — Building the frequency map takes O(n), and the sliding window visits each character in `s` at most twice.

      **Space Complexity:** O(m + n) — Hash maps for character frequencies. In practice O(52) for English letters only.

      This solution uses the classic sliding window pattern: expand until valid, then contract to find the minimum. The key insight is tracking `have` vs `required` to know when the window is valid in O(1) time.

  - approach_name: Optimised Sliding Window (Filtered Characters)
    is_optimal: true
    code: |
      from collections import Counter

      def min_window(s: str, t: str) -> str:
          if not t or not s:
              return ""

          need = Counter(t)
          required = len(need)

          # Filter s to only keep relevant characters with their indices
          filtered = [(i, char) for i, char in enumerate(s) if char in need]

          window = {}
          have = 0
          result = (float("inf"), 0, 0)

          left = 0
          for right in range(len(filtered)):
              # Expand window
              idx_right, char = filtered[right]
              window[char] = window.get(char, 0) + 1

              if window[char] == need[char]:
                  have += 1

              # Contract window
              while have == required:
                  idx_left = filtered[left][0]
                  window_len = idx_right - idx_left + 1

                  if window_len < result[0]:
                      result = (window_len, idx_left, idx_right)

                  left_char = filtered[left][1]
                  window[left_char] -= 1

                  if window[left_char] < need[left_char]:
                      have -= 1

                  left += 1

          length, start, end = result
          return s[start:end + 1] if length != float("inf") else ""
    explanation: |
      **Time Complexity:** O(m + n) — Same asymptotic complexity, but faster in practice when `s` has many characters not in `t`.

      **Space Complexity:** O(m + n) — Additional space for the filtered list.

      This optimisation pre-filters `s` to only include characters that appear in `t`. When `s` is much longer than `t` and contains many irrelevant characters, this reduces the number of iterations significantly.

  - approach_name: Brute Force
    is_optimal: false
    code: |
      from collections import Counter

      def min_window(s: str, t: str) -> str:
          def contains_all(sub: str, target: str) -> bool:
              """Check if substring contains all characters from target."""
              sub_count = Counter(sub)
              target_count = Counter(target)
              for char, count in target_count.items():
                  if sub_count.get(char, 0) < count:
                      return False
              return True

          m, n = len(s), len(t)
          if n > m:
              return ""

          # Try all substrings, starting from smallest possible
          for length in range(n, m + 1):
              for start in range(m - length + 1):
                  substring = s[start:start + length]
                  if contains_all(substring, t):
                      return substring

          return ""
    explanation: |
      **Time Complexity:** O(m^2 * n) — We check O(m^2) substrings, each taking O(m + n) to validate.

      **Space Complexity:** O(m + n) — Counter objects for each substring comparison.

      This brute force approach tries all possible substrings in order of increasing length. While correct, it's far too slow for the given constraints and will result in TLE. Included to illustrate why the sliding window approach is necessary.
