727. Minimum Window Subsequence 🔒
Problem Description
You are given two strings s1
and s2
. Your task is to find the minimum contiguous substring of s1
such that s2
is a subsequence of this substring.
A subsequence means that the characters of s2
appear in the substring in the same order, but not necessarily consecutively. For example, "ace" is a subsequence of "aecace" (the bold part shows one valid window).
The requirements are:
- Find the shortest contiguous substring of
s1
that contains all characters ofs2
as a subsequence - If multiple substrings of the same minimum length exist, return the one that starts earliest (leftmost) in
s1
- If no such substring exists, return an empty string
""
For example:
- If
s1 = "abcdebdde"
ands2 = "bde"
, the answer would be"bcde"
because:- The substring
"bcde"
containss2 = "bde"
as a subsequence - While
"bdde"
at the end also contains"bde"
as a subsequence and has the same length,"bcde"
appears first
- The substring
The solution uses dynamic programming where f[i][j]
tracks the starting position of the shortest substring ending at position i
in s1
that contains the first j
characters of s2
as a subsequence. The algorithm builds this table and then finds the minimum length window that contains all of s2
.
Intuition
The key insight is that we need to track where valid subsequences start as we scan through s1
. Since we're looking for the minimum window containing s2
as a subsequence, we need to know: "If I'm at position i
in s1
and I've matched j
characters of s2
, where did this matching sequence start?"
Think about it this way: as we scan through s1
, at each position we can potentially match characters from s2
. When we match a character, we're either:
- Starting a new subsequence (matching the first character of
s2
) - Extending an existing subsequence (matching the next character of
s2
)
This naturally leads to a dynamic programming approach where we track the starting positions of valid subsequences.
The brilliant part is using f[i][j]
to store the starting position (not just a boolean or count). Why? Because once we've matched all characters of s2
(reached j = n
), we need to know where this subsequence started to calculate the window length.
Consider matching s2 = "bde"
in s1 = "abcdebdde"
:
- When we see 'b' at position 2, we can start matching
s2
- When we see 'd' at position 4, we can extend our match if we had 'b' before
- When we see 'e' at position 5, we complete the match and know it started at position 2
The state transition naturally follows:
- If characters match and it's the first character of
s2
, record current position as start - If characters match and it's not the first character, inherit the start position from the previous match
- If characters don't match, keep the previous best start position for this many matched characters
Finally, we scan through all positions where we've matched all of s2
and pick the shortest window.
Learn more about Dynamic Programming and Sliding Window patterns.
Solution Approach
We implement the solution using a 2D dynamic programming table f[i][j]
where:
i
represents the position in strings1
(from 0 to m)j
represents the position in strings2
(from 0 to n)f[i][j]
stores the starting position (1-indexed) of the shortest substring ofs1[0...i-1]
that containss2[0...j-1]
as a subsequence
Step 1: Initialize the DP table
f = [[0] * (n + 1) for _ in range(m + 1)]
We create a table of size (m+1) × (n+1)
initialized with zeros. Zero indicates no valid subsequence exists.
Step 2: Fill the DP table
For each position i
in s1
and each position j
in s2
:
if a == b: # s1[i-1] == s2[j-1] f[i][j] = i if j == 1 else f[i - 1][j - 1] else: f[i][j] = f[i - 1][j]
The state transition follows these rules:
- When
s1[i-1] == s2[j-1]
:- If
j == 1
(matching first character ofs2
): Setf[i][j] = i
(start a new subsequence here) - If
j > 1
(matching subsequent characters): Setf[i][j] = f[i-1][j-1]
(inherit start position from previous match)
- If
- When
s1[i-1] != s2[j-1]
:- Set
f[i][j] = f[i-1][j]
(keep the best start position found so far for matchingj
characters)
- Set
Step 3: Find the minimum window
p, k = 0, m + 1
for i, a in enumerate(s1, 1):
if a == s2[n - 1] and f[i][n]:
j = f[i][n] - 1
if i - j < k:
k = i - j
p = j
We scan through s1
looking for positions where:
- The character matches the last character of
s2
- We have a valid subsequence ending here (
f[i][n] > 0
)
For each valid ending position, we calculate the window length as i - (f[i][n] - 1)
and keep track of the minimum.
Step 4: Return the result
return "" if k > m else s1[p : p + k]
If no valid window was found (k > m
), return empty string. Otherwise, return the substring from position p
with length k
.
Time Complexity: O(m × n) where m = len(s1) and n = len(s2)
Space Complexity: O(m × n) for the DP table
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with s1 = "abcdebdde"
and s2 = "bde"
.
Setup:
m = 9
(length of s1),n = 3
(length of s2)- Initialize DP table
f[10][4]
with all zeros
Building the DP Table:
We'll process each character of s1 and track where valid subsequences start.
i | s1[i-1] | j=1 (need 'b') | j=2 (need 'd') | j=3 (need 'e') |
---|---|---|---|---|
1 | 'a' | f[1][1]=0 (no match) | f[1][2]=0 | f[1][3]=0 |
2 | 'b' | f[2][1]=2 (match! start here) | f[2][2]=0 | f[2][3]=0 |
3 | 'c' | f[3][1]=2 (keep previous) | f[3][2]=0 | f[3][3]=0 |
4 | 'd' | f[4][1]=2 | f[4][2]=2 (match! inherit from f[3][1]) | f[4][3]=0 |
5 | 'e' | f[5][1]=2 | f[5][2]=2 | f[5][3]=2 (match! inherit from f[4][2]) |
6 | 'b' | f[6][1]=6 (new 'b' starts here) | f[6][2]=2 | f[6][3]=2 |
7 | 'd' | f[7][1]=6 | f[7][2]=6 (match! inherit from f[6][1]) | f[7][3]=2 |
8 | 'd' | f[8][1]=6 | f[8][2]=6 (match! inherit from f[7][1]) | f[8][3]=2 |
9 | 'e' | f[9][1]=6 | f[9][2]=6 | f[9][3]=6 (match! inherit from f[8][2]) |
Key observations:
- At i=2: 'b' matches s2[0], so we start a new subsequence (f[2][1]=2)
- At i=4: 'd' matches s2[1], and we had 'b' before, so f[4][2]=2 (inherited from f[3][1])
- At i=5: 'e' matches s2[2], completing "bde" starting from position 2
- At i=6: Another 'b' starts a new potential subsequence
- At i=9: 'e' completes another "bde" sequence starting from position 6
Finding the Minimum Window:
Now we scan for positions where we've matched all of s2 (j=3):
-
At i=5: s1[4]='e' matches s2[2], and f[5][3]=2
- Window: s1[1:5] = "bcde" (positions 2-5)
- Length: 5 - 2 + 1 = 4
-
At i=9: s1[8]='e' matches s2[2], and f[9][3]=6
- Window: s1[5:9] = "bdde" (positions 6-9)
- Length: 9 - 6 + 1 = 4
Both windows have length 4, but "bcde" starts earlier (position 1 vs position 5 in 0-indexed), so we return "bcde".
Why the DP approach works:
f[i][j]
tracks where the shortest valid subsequence starts, allowing us to calculate window lengths- By processing left-to-right, we naturally find the leftmost window when lengths are equal
- The state transitions ensure we only extend valid subsequences and always keep the best (earliest) starting position
Solution Implementation
1class Solution:
2 def minWindow(self, s1: str, s2: str) -> str:
3 len_s1, len_s2 = len(s1), len(s2)
4
5 # dp[i][j] stores the starting index (1-based) in s1 where
6 # the subsequence s2[0:j] ends at position i in s1
7 # 0 means no valid subsequence found
8 dp = [[0] * (len_s2 + 1) for _ in range(len_s1 + 1)]
9
10 # Fill the dp table
11 for i in range(1, len_s1 + 1):
12 for j in range(1, len_s2 + 1):
13 if s1[i - 1] == s2[j - 1]:
14 # Characters match
15 if j == 1:
16 # First character of s2, start index is current position
17 dp[i][j] = i
18 else:
19 # Continue from previous subsequence
20 dp[i][j] = dp[i - 1][j - 1]
21 else:
22 # Characters don't match, inherit from previous position in s1
23 dp[i][j] = dp[i - 1][j]
24
25 # Find the minimum window
26 start_index = 0
27 min_length = len_s1 + 1
28
29 for i in range(1, len_s1 + 1):
30 # Check if s1[i-1] matches the last character of s2
31 # and we have a valid subsequence ending at position i
32 if s1[i - 1] == s2[len_s2 - 1] and dp[i][len_s2] != 0:
33 # Calculate window start (0-based) and length
34 window_start = dp[i][len_s2] - 1
35 window_length = i - window_start
36
37 # Update minimum window if current one is smaller
38 if window_length < min_length:
39 min_length = window_length
40 start_index = window_start
41
42 # Return the minimum window substring or empty string if not found
43 return "" if min_length > len_s1 else s1[start_index : start_index + min_length]
44
1class Solution {
2 public String minWindow(String s1, String s2) {
3 int s1Length = s1.length();
4 int s2Length = s2.length();
5
6 // dp[i][j] stores the starting index (1-based) in s1 where a valid subsequence
7 // matching s2[0...j-1] ends at position i-1 in s1
8 // If dp[i][j] = 0, no valid subsequence exists
9 int[][] dp = new int[s1Length + 1][s2Length + 1];
10
11 // Build the DP table
12 for (int i = 1; i <= s1Length; i++) {
13 for (int j = 1; j <= s2Length; j++) {
14 if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
15 // Characters match
16 if (j == 1) {
17 // First character of s2, so the subsequence starts at current position
18 dp[i][j] = i;
19 } else {
20 // Continue from previous match
21 dp[i][j] = dp[i - 1][j - 1];
22 }
23 } else {
24 // Characters don't match, inherit from previous position in s1
25 dp[i][j] = dp[i - 1][j];
26 }
27 }
28 }
29
30 // Find the minimum window
31 int startIndex = 0;
32 int minWindowLength = s1Length + 1;
33
34 for (int i = 1; i <= s1Length; i++) {
35 // Check if current position can be the end of a valid window
36 if (s1.charAt(i - 1) == s2.charAt(s2Length - 1) && dp[i][s2Length] > 0) {
37 // Calculate window size
38 int windowStart = dp[i][s2Length] - 1; // Convert to 0-based index
39 int currentWindowLength = i - windowStart;
40
41 // Update minimum window if current one is smaller
42 if (currentWindowLength < minWindowLength) {
43 minWindowLength = currentWindowLength;
44 startIndex = windowStart;
45 }
46 }
47 }
48
49 // Return the minimum window substring, or empty string if no valid window exists
50 return minWindowLength > s1Length ? "" : s1.substring(startIndex, startIndex + minWindowLength);
51 }
52}
53
1class Solution {
2public:
3 string minWindow(string s1, string s2) {
4 int m = s1.size();
5 int n = s2.size();
6
7 // dp[i][j] stores the starting index (1-based) of the substring in s1[0...i-1]
8 // that ends at position i-1 and contains s2[0...j-1] as a subsequence
9 // If no such substring exists, dp[i][j] = 0
10 int dp[m + 1][n + 1];
11 memset(dp, 0, sizeof(dp));
12
13 // Build the DP table
14 for (int i = 1; i <= m; ++i) {
15 for (int j = 1; j <= n; ++j) {
16 if (s1[i - 1] == s2[j - 1]) {
17 // If characters match
18 if (j == 1) {
19 // First character of s2, record current position as start
20 dp[i][j] = i;
21 } else {
22 // Continue from previous subsequence match
23 dp[i][j] = dp[i - 1][j - 1];
24 }
25 } else {
26 // Characters don't match, inherit from previous position in s1
27 dp[i][j] = dp[i - 1][j];
28 }
29 }
30 }
31
32 // Find the minimum window
33 int startPos = 0; // Starting position of minimum window
34 int minLength = m + 1; // Length of minimum window
35
36 for (int i = 1; i <= m; ++i) {
37 // Check if s1[i-1] matches the last character of s2
38 // and a valid subsequence ending at position i exists
39 if (s1[i - 1] == s2[n - 1] && dp[i][n] != 0) {
40 int windowStart = dp[i][n] - 1; // Convert to 0-based index
41 int windowLength = i - windowStart;
42
43 // Update minimum window if current one is smaller
44 if (windowLength < minLength) {
45 minLength = windowLength;
46 startPos = windowStart;
47 }
48 }
49 }
50
51 // Return the minimum window substring, or empty string if not found
52 return minLength > m ? "" : s1.substr(startPos, minLength);
53 }
54};
55
1/**
2 * Finds the minimum window substring in s1 that contains all characters of s2 in order
3 * @param s1 - The source string to search in
4 * @param s2 - The target string whose characters must appear in order
5 * @returns The minimum window substring, or empty string if not found
6 */
7function minWindow(s1: string, s2: string): string {
8 const sourceLength: number = s1.length;
9 const targetLength: number = s2.length;
10
11 // dp[i][j] represents the starting index (1-based) in s1 where
12 // the first j characters of s2 can be found as a subsequence
13 // within s1[0...i-1]
14 const dp: number[][] = Array(sourceLength + 1)
15 .fill(0)
16 .map(() => Array(targetLength + 1).fill(0));
17
18 // Build the DP table
19 for (let i = 1; i <= sourceLength; i++) {
20 for (let j = 1; j <= targetLength; j++) {
21 if (s1[i - 1] === s2[j - 1]) {
22 // Characters match
23 if (j === 1) {
24 // First character of s2 found, record current position
25 dp[i][j] = i;
26 } else {
27 // Continue from previous subsequence
28 dp[i][j] = dp[i - 1][j - 1];
29 }
30 } else {
31 // Characters don't match, inherit from previous position in s1
32 dp[i][j] = dp[i - 1][j];
33 }
34 }
35 }
36
37 // Find the minimum window
38 let startIndex: number = 0;
39 let minWindowLength: number = sourceLength + 1;
40
41 for (let i = 1; i <= sourceLength; i++) {
42 // Check if current position ends with last character of s2
43 // and we have a valid subsequence
44 if (s1[i - 1] === s2[targetLength - 1] && dp[i][targetLength] > 0) {
45 const windowStart: number = dp[i][targetLength] - 1;
46 const currentWindowLength: number = i - windowStart;
47
48 // Update minimum window if current one is smaller
49 if (currentWindowLength < minWindowLength) {
50 minWindowLength = currentWindowLength;
51 startIndex = windowStart;
52 }
53 }
54 }
55
56 // Return the minimum window substring or empty string if not found
57 return minWindowLength > sourceLength ? '' : s1.slice(startIndex, startIndex + minWindowLength);
58}
59
Time and Space Complexity
The time complexity is O(m × n)
, where m
is the length of string s1
and n
is the length of string s2
. This is because:
- The code contains two nested loops that iterate through
s1
(lengthm
) ands2
(lengthn
) to fill the DP tablef
, resulting inO(m × n)
operations. - The second loop iterates through
s1
once more withO(m)
operations, but this is dominated by theO(m × n)
complexity.
The space complexity is O(m × n)
due to the 2D DP table f
which has dimensions (m + 1) × (n + 1)
, storing the starting positions for minimum window subsequences.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Index Conversion Between 1-based and 0-based Systems
The most common pitfall in this solution is mixing up 1-based indexing (used in the DP table) with 0-based indexing (used for string access). The DP table stores 1-based indices to distinguish between "no valid subsequence" (0) and "valid subsequence starting at position 0" (which would be 1 in the table).
Pitfall Example:
# WRONG: Forgetting to subtract 1 when converting from DP index to string index window_start = dp[i][len_s2] # This is 1-based! return s1[window_start : window_start + min_length] # Error: using 1-based index in 0-based string
Solution: Always remember to subtract 1 when converting from DP table indices to string indices:
window_start = dp[i][len_s2] - 1 # Convert to 0-based
2. Not Checking for Valid Subsequence Before Calculating Window
Another pitfall is attempting to calculate the window length when dp[i][len_s2] == 0
, which means no valid subsequence exists.
Pitfall Example:
# WRONG: Not checking if dp[i][len_s2] is non-zero if s1[i - 1] == s2[len_s2 - 1]: # Missing check for dp[i][len_s2] != 0 window_start = dp[i][len_s2] - 1 # This could be -1 if dp[i][len_s2] is 0!
Solution: Always verify that a valid subsequence exists before processing:
if s1[i - 1] == s2[len_s2 - 1] and dp[i][len_s2] != 0: window_start = dp[i][len_s2] - 1
3. Incorrect Window Length Calculation
The window length should be calculated as the difference between the current ending position and the starting position.
Pitfall Example:
# WRONG: Using 1-based index for current position in calculation window_length = i - dp[i][len_s2] + 1 # Incorrect formula
Solution:
The correct calculation accounts for the fact that i
is 1-based and window_start
is converted to 0-based:
window_start = dp[i][len_s2] - 1 # 0-based start window_length = i - window_start # i is 1-based, so this gives the correct length
4. Edge Case: Empty Strings or s2 Longer Than s1
Failing to handle edge cases where s2
is empty or longer than s1
can cause issues.
Solution: Add validation at the beginning:
if not s2:
return ""
if len(s2) > len(s1):
return ""
5. Misunderstanding the DP State Definition
A conceptual pitfall is misunderstanding what dp[i][j]
represents. It's crucial to remember that it stores the starting position (1-based) of the shortest substring ending at position i
that contains the first j
characters of s2
as a subsequence.
Pitfall Example:
# WRONG: Thinking dp[i][j] stores the ending position or length min_length = dp[i][len_s2] # This is NOT the length!
Solution:
Always remember that dp[i][j]
is a starting position, and calculate the length as:
window_length = i - (dp[i][len_s2] - 1) # Current position minus start position
Which of these pictures shows the visit order of a depth-first search?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!