Facebook Pixel

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 of s2 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" and s2 = "bde", the answer would be "bcde" because:
    • The substring "bcde" contains s2 = "bde" as a subsequence
    • While "bdde" at the end also contains "bde" as a subsequence and has the same length, "bcde" appears first

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Starting a new subsequence (matching the first character of s2)
  2. 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 string s1 (from 0 to m)
  • j represents the position in string s2 (from 0 to n)
  • f[i][j] stores the starting position (1-indexed) of the shortest substring of s1[0...i-1] that contains s2[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 of s2): Set f[i][j] = i (start a new subsequence here)
    • If j > 1 (matching subsequent characters): Set f[i][j] = f[i-1][j-1] (inherit start position from previous match)
  • When s1[i-1] != s2[j-1]:
    • Set f[i][j] = f[i-1][j] (keep the best start position found so far for matching j characters)

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 Evaluator

Example 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.

is1[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]=0f[1][3]=0
2'b'f[2][1]=2 (match! start here)f[2][2]=0f[2][3]=0
3'c'f[3][1]=2 (keep previous)f[3][2]=0f[3][3]=0
4'd'f[4][1]=2f[4][2]=2 (match! inherit from f[3][1])f[4][3]=0
5'e'f[5][1]=2f[5][2]=2f[5][3]=2 (match! inherit from f[4][2])
6'b'f[6][1]=6 (new 'b' starts here)f[6][2]=2f[6][3]=2
7'd'f[7][1]=6f[7][2]=6 (match! inherit from f[6][1])f[7][3]=2
8'd'f[8][1]=6f[8][2]=6 (match! inherit from f[7][1])f[8][3]=2
9'e'f[9][1]=6f[9][2]=6f[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):

  1. 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
  2. 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 (length m) and s2 (length n) to fill the DP table f, resulting in O(m × n) operations.
  • The second loop iterates through s1 once more with O(m) operations, but this is dominated by the O(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
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of these pictures shows the visit order of a depth-first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More