Minimum Window Subsequence mixes two-pointer scanning with a clever "match forward, then snap backward" move. It is a favorite because the obvious DP is heavy, but a tidy two-pass scan beats it for most inputs and is far easier to explain.
Problem. Given strings S and T, find the shortest contiguous substring of S that contains
T as a subsequence (the characters of T appear in order, but not necessarily adjacent). If there
is no such window, return "". If several windows tie for shortest, return the leftmost.
Example: S = "abcdebdde", T = "bde" → answer "bcde" (indices 1..4: b _ c _ d _ e covers b, d, e in order).
The slow way first
The textbook approach is dynamic programming: dp[i][j] = the start index of the smallest window of S[0..i] that contains T[0..j]. Filling that grid is O(|S| · |T|) time and space. It works, but it is a lot of bookkeeping for an interview whiteboard, and the space can hurt.
The question to ask: do I really need a 2-D table, or can I just walk the string? It turns out a two-pointer scan finds every candidate window directly.
The idea: match forward, then snap backward
Scan S left to right with hi, advancing a cursor through T each time the characters match. The moment the cursor reaches the end of T, every character of T has been covered somewhere in S[start..hi] — but start may be loose (there could be junk on the left). So we walk backward from hi, matching T in reverse, until the cursor empties. Wherever that backward walk stops is the tightest left edge lo.
After recording the window, restart the forward scan from lo + 1 so we never miss a later, possibly shorter, window.
Walk through it
Step through the animation. The hi pointer scans S and lights up b (index 1), d (index 3), e (index 4) — that completes T = "bde". Then lo starts at hi and walks left, matching e, d, skipping c, and finally b at index 1. The window S[1..4] = "bcde" is recorded as the best.
Pseudocode
best = ""
i = 0
while i is inside S:
# forward: advance i, matching each char of T in order
match T from its start by scanning forward; if T is not fully matched, stop
hi = i # right edge of a valid window
# backward: from hi, match T in reverse until T is consumed
walk i left, consuming T from its end; stop when all of T is matched
lo = i # tightest left edge
if best is empty or window (lo..hi) is shorter:
best = S[lo..hi]
i = lo + 1 # restart just past the left edge
return bestThe Python solution
def min_window(S, T):
best = ""
i = 0
while i < len(S):
t = 0
while i < len(S):
if S[i] == T[t]:
t += 1
if t == len(T):
break
i += 1
if t < len(T):
break
hi = i
t = len(T) - 1
while t >= 0:
if S[i] == T[t]:
t -= 1
i -= 1
i += 1
if best == "" or hi - i + 1 < len(best):
best = S[i:hi + 1]
i += 1
return best- The first inner
whileis the forward scan: it advancesiand the T-cursort, breaking the momentt == len(T)— meaningTis fully covered. if t < len(T): breakhandles the tail ofSwhereTcan no longer be completed — we are done.hi = icaptures the right edge of this valid window.- The second inner
whileis the backward walk: starting fromhi, it consumesTfrom its last character backward. When it ends,isits just before the tightest start, soi += 1landsiexactly onlo. - We compare
hi - i + 1(the window length) against the best so far and keep the shorter one;i += 1restarts the forward scan one past the left edge.
Complexity
| Case | Time | Notes |
|---|---|---|
| DP table | O(|S| · |T|) (moderate) | fill a 2-D grid |
| Forward + backward scan | O(|S| · |T|) (moderate) | but tiny constants, O(1) extra space |
O(1) (fast)Both are the same big-O on paper, but the two-pointer scan uses only a couple of indices instead of a full grid, so it is far lighter in practice — and much easier to derive live.
When this pattern shows up
Whenever a window must contain a target in order (a subsequence, not a set), think "scan forward to find a valid right edge, then walk backward to tighten the left edge." The same forward-then-backward snap appears in subsequence-matching and shortest-supersequence problems.
Do not confuse this with Minimum Window Substring, where T is a multiset of characters that can appear
in any order — that one uses a sliding window with counts. Here order matters, so a plain sliding window is
not enough; the backward walk is what makes the window tight.
Practice
After the forward scan completes T at hi = 4 in S = 'abcdebdde', where does the backward walk stop, and what window does it produce?
1. Why do we walk backward after the forward scan finds a match?
2. How does this problem differ from Minimum Window Substring?
3. Where does the forward scan restart after recording a window at left edge lo?
4. What is the extra space used by the two-pointer scan?