Stage 1: Foundations·Two Pointers & Sliding Window
Hard#768 min readSliding WindowHashingLeetCode

Minimum Window Substring

Counter-Based Sliding Window

The ‘hard’ sliding window — but if you understood #3, this is the same pattern with a counter.

Published Apr 15, 2026

Problem

Given two strings s and t, return the minimum window substring of s such that every character in t appears in the window with the same multiplicity.

If no such window exists, return the empty string "".

Input
s = "ADOBECODEBANC", t = "ABC"
Output
"BANC"
Explanation
BANC is the smallest substring containing A, B, and C.
Input
s = "a", t = "a"
Output
"a"
Input
s = "a", t = "aa"
Output
""

Intuition

This problem is a sliding window, but not the "find a substring with no duplicates" version from #3. Here the window is valid only when it contains enough of every required character.

So instead of a boolean invariant, we maintain a deficit counter:

  • need[c] tells us how many more of character c the window still needs.
  • When we add a character on the right, we reduce its deficit.
  • When all deficits are satisfied, we try to shrink from the left as much as possible while staying valid.

That gives us the minimum window for every right boundary, and the best among those is the answer.

Approach

Counter-Based Sliding Window

TimeO(n + m)
SpaceO(1)
Recommended

Build the requirement counts from t, then sweep s with two pointers.

  • Expand right until the window covers t.
  • Once valid, shrink left while the window still covers t.
  • Record the smallest valid window seen so far.

The window is valid when the number of still-missing characters reaches zero. Repeated characters are handled automatically because the counter tracks multiplicity, not just presence.

impl Solution {
    pub fn min_window(s: String, t: String) -> String {
        if t.len() > s.len() {
            return String::new();
        }

        let sb = s.as_bytes();
        let tb = t.as_bytes();
        let mut need = [0i32; 128];
        for &b in tb {
            need[b as usize] += 1;
        }

        let mut missing = tb.len() as i32;
        let mut left = 0usize;
        let mut best_len = usize::MAX;
        let mut best_start = 0usize;

        for right in 0..sb.len() {
            let idx = sb[right] as usize;
            need[idx] -= 1;
            if need[idx] >= 0 {
                missing -= 1;
            }

            while missing == 0 {
                let window_len = right - left + 1;
                if window_len < best_len {
                    best_len = window_len;
                    best_start = left;
                }

                let left_idx = sb[left] as usize;
                need[left_idx] += 1;
                if need[left_idx] > 0 {
                    missing += 1;
                }
                left += 1;
            }
        }

        if best_len == usize::MAX {
            String::new()
        } else {
            s[best_start..best_start + best_len].to_string()
        }
    }
}
Rust · runnable

The important detail is the sign convention on need: positive means the window is still missing that many characters, zero means satisfied, negative means the window has surplus copies.

Trace

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

rightcharmissingactionbest
0A2gain A-
1D2surplus-
2O2surplus-
3B1gain B-
4E1surplus-
5C0window valid, shrink left to 1ADOBEC
6-9......keep shrinking and expandingADOBEC
10A0window valid againBANC

The first valid window is not the answer; it is only the first chance to shrink.

Edge cases

  • No possible window returns "".
  • Repeated letters in t matter. t = "AABC" requires two As, not one.
  • t longer than s can short-circuit immediately.
  • Surplus characters inside the window are allowed as long as the deficits stay covered.
💡
Tip

If #3 is "maintain a set of characters that are inside the window," #76 is "maintain a multiset of required characters that the window still owes." Same loop, stricter invariant.

Takeaway

The difference between easy and hard sliding window problems is usually the invariant, not the loop shape.

Once you can phrase the window state as a counter of remaining requirements, minimum-window problems stop being mysterious and become a mechanical shrink-and-record scan.

More in Two Pointers & Sliding Window