Minimum Window Subsequence
Smallest substring of S containing T as a subsequence (not contiguously) — two-pointer back-and-forth scan.
Problem#
Given strings s and t, find the smallest substring W of s such that t is a subsequence of W. If multiple, return the leftmost.
Examples#
s = "abcdebdde", t = "bde"→"bcde"s = "jmeqksfrsdcmsiwvaovztaqenprpvnbstl", t = "u"→""
Constraints#
1 <= s.length <= 2 * 10^4,1 <= t.length <= 100.
Hints#
Hint 1
Approach#
Two-pass per anchor: forward to find the earliest match-end r containing t as a subsequence starting at some l; then backward from r to find the latest match-start l' still consuming all of t. That gives the tightest window ending at r; advance to look for shorter windows.
Solution#
class Solution {public: string minWindow(string s, string t) { const int n = s.size(), m = t.size(); int bestL = -1, bestLen = INT_MAX; int i = 0; while (i < n) { int j = 0; while (i < n && j < m) { if (s[i] == t[j]) ++j; ++i; } if (j < m) break; // Backward tighten from i-1. int r = i - 1, k = m - 1; while (k >= 0) { if (s[r] == t[k]) --k; --r; } int l = r + 1; if (i - l < bestLen) { bestLen = i - l; bestL = l; } i = l + 1; } return bestL < 0 ? "" : s.substr(bestL, bestLen); }};func minWindow(s string, t string) string { n, m := len(s), len(t) bestL, bestLen := -1, math.MaxInt32 i := 0 for i < n { j := 0 for i < n && j < m { if s[i] == t[j] { j++ } i++ } if j < m { break } r, k := i-1, m-1 for k >= 0 { if s[r] == t[k] { k-- } r-- } l := r + 1 if i-l < bestLen { bestLen = i - l bestL = l } i = l + 1 } if bestL < 0 { return "" } return s[bestL : bestL+bestLen]}class Solution: def minWindow(self, s: str, t: str) -> str: n, m = len(s), len(t) best_l, best_len = -1, float('inf') i = 0 while i < n: j = 0 while i < n and j < m: if s[i] == t[j]: j += 1 i += 1 if j < m: break r, k = i - 1, m - 1 while k >= 0: if s[r] == t[k]: k -= 1 r -= 1 l = r + 1 if i - l < best_len: best_len = i - l best_l = l i = l + 1 if best_l < 0: return "" return s[best_l:best_l + best_len]function minWindow(s, t) { const n = s.length, m = t.length; let bestL = -1, bestLen = Infinity; let i = 0; while (i < n) { let j = 0; while (i < n && j < m) { if (s[i] === t[j]) j++; i++; } if (j < m) break; let r = i - 1, k = m - 1; while (k >= 0) { if (s[r] === t[k]) k--; r--; } const l = r + 1; if (i - l < bestLen) { bestLen = i - l; bestL = l; } i = l + 1; } return bestL < 0 ? "" : s.substring(bestL, bestL + bestLen);}class Solution { public String minWindow(String s, String t) { int n = s.length(), m = t.length(); int bestL = -1, bestLen = Integer.MAX_VALUE; int i = 0; while (i < n) { int j = 0; while (i < n && j < m) { if (s.charAt(i) == t.charAt(j)) j++; i++; } if (j < m) break; int r = i - 1, k = m - 1; while (k >= 0) { if (s.charAt(r) == t.charAt(k)) k--; r--; } int l = r + 1; if (i - l < bestLen) { bestLen = i - l; bestL = l; } i = l + 1; } return bestL < 0 ? "" : s.substring(bestL, bestL + bestLen); }}function minWindow(s: string, t: string): string { const n = s.length, m = t.length; let bestL = -1, bestLen = Infinity; let i = 0; while (i < n) { let j = 0; while (i < n && j < m) { if (s[i] === t[j]) j++; i++; } if (j < m) break; let r = i - 1, k = m - 1; while (k >= 0) { if (s[r] === t[k]) k--; r--; } const l = r + 1; if (i - l < bestLen) { bestLen = i - l; bestL = l; } i = l + 1; } return bestL < 0 ? "" : s.substring(bestL, bestL + bestLen);}Editorial#
Sliding-window over multisets doesn’t apply here — subsequence matching is order-sensitive. The forward/backward two-pass gives the canonical greedy: find any window ending at r (forward), then move the start as far right as possible while preserving the match (backward). Restarting the search at l + 1 rather than l is what avoids quadratic re-scan for many inputs in practice.
Complexity#
- Time: O(n × m) worst case.
- Space: O(1).
Concept revision#
Related#