| title | keywords🏷️ | author | description | date | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
🔍 Minimum Window Subsequence | GFG Solution 🎯 |
|
✍️ Het Patel (Hunterdii) |
✅ GFG solution to the Minimum Window Subsequence problem: find the smallest substring in s1 that contains s2 as a subsequence using greedy backtracking and two-pointer techniques. 🚀 |
📅 2025-01-11 |
The problem can be found at the following link: 🔗 Question Link
You are given two strings, s1 and s2. Your task is to find the smallest substring in s1 such that s2 appears as a subsequence within that substring.
- The characters of
s2must appear in the same sequence within the substring ofs1. - If there are multiple valid substrings of the same minimum length, return the one that appears first in
s1. - If no such substring exists, return an empty string.
Note: Both the strings contain only lowercase English letters.
Input: s1 = "geeksforgeeks", s2 = "eksrg"
Output: "eksforg"
Explanation: "eksforg" satisfies all required conditions. s2 is its subsequence and it is
smallest and leftmost among all possible valid substrings of s1.Input: s1 = "abcdebdde", s2 = "bde"
Output: "bcde"
Explanation: "bcde" and "bdde" are two substrings of s1 where s2 occurs as subsequence
but "bcde" occurs first so we return that.Input: s1 = "ad", s2 = "b"
Output: ""
Explanation: There is no substring that exists.$1 \le \text{s1.length} \le 10^4$ $1 \le \text{s2.length} \le 50$
The optimal approach uses a Greedy Backtracking with Two-Pointer technique to efficiently find the minimum window subsequence:
-
Forward Pass - Find Complete Subsequence:
- Use two pointers:
ifors1andjfors2. - Move
iforward throughs1, incrementingjwhenever characters match. - When
jreaches the end ofs2, we've found a valid window endpoint.
- Use two pointers:
-
Backward Pass - Minimize Window:
- Once a complete subsequence is found, backtrack from the current position.
- Move
ibackward while decrementingjto find the leftmost starting position. - This gives us the minimum window for this particular endpoint.
-
Track Minimum Window:
- Compare the current window length with the global minimum.
- Update the result if a smaller window is found.
- Continue the forward pass to explore other potential windows.
-
Optimization:
- After each backtrack, resume the forward search from the optimized starting position.
- This ensures we don't miss any potentially smaller windows.
- Expected Time Complexity: O(m × n), where m is the length of
s1and n is the length ofs2. In the worst case, for each character ins1, we might backtrack through the entire subsequences2. - Expected Auxiliary Space Complexity: O(1), as we only use a constant amount of additional space for pointers and variables, regardless of the input size.
class Solution {
public:
string minWindow(string& s1, string& s2) {
int m = s1.size(), n = s2.size();
int start = -1, minLen = INT_MAX;
for (int i = 0, j = 0; i < m; i++) {
if (s1[i] == s2[j]) j++;
if (j == n) {
int end = i;
j--;
while (j >= 0) {
if (s1[i] == s2[j]) j--;
i--;
}
i++;
j++;
if (end - i + 1 < minLen) {
minLen = end - i + 1;
start = i;
}
}
}
return start == -1 ? "" : s1.substr(start, minLen);
}
};⚡ View Alternative Approaches with Code and Analysis
- Build a DP table storing the starting index of each character match.
- For each position, track where the subsequence starting point is.
- Iterate through all valid endpoints to find minimum window.
- Return the smallest substring containing the subsequence.
class Solution {
public:
string minWindow(string& s1, string& s2) {
int m = s1.size(), n = s2.size();
vector<vector<int>> dp(m, vector<int>(n, -1));
for (int i = 0; i < m; i++) {
if (s1[i] == s2[0]) dp[i][0] = i;
else if (i > 0) dp[i][0] = dp[i-1][0];
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (s1[i] == s2[j]) dp[i][j] = dp[i-1][j-1];
else dp[i][j] = dp[i-1][j];
}
}
int start = -1, minLen = INT_MAX;
for (int i = 0; i < m; i++) {
if (dp[i][n-1] != -1) {
int len = i - dp[i][n-1] + 1;
if (len < minLen) {
minLen = len;
start = dp[i][n-1];
}
}
}
return start == -1 ? "" : s1.substr(start, minLen);
}
};- Time: ⏱️ O(m × n) - Fill entire DP table
- Auxiliary Space: 💾 O(m × n) - 2D DP array storage
- Systematic tabulation of all possibilities
- Clear state transitions and relationships
- Works well for understanding the problem structure
- Precompute next occurrence of each character from every position.
- For each starting position, greedily find the closest match.
- Track the minimum window length during traversal.
- Use next index array for O(1) character lookups.
class Solution {
public:
string minWindow(string& s1, string& s2) {
int m = s1.size(), n = s2.size();
vector<vector<int>> next(m+1, vector<int>(26, -1));
for (int i = m-1; i >= 0; i--) {
next[i] = next[i+1];
next[i][s1[i]-'a'] = i;
}
int start = -1, minLen = INT_MAX;
for (int i = 0; i < m; i++) {
int pos = i, j = 0;
while (j < n && pos < m) {
pos = next[pos][s2[j]-'a'];
if (pos == -1) break;
j++;
if (j < n) pos++;
}
if (j == n) {
int len = pos - i + 1;
if (len < minLen) {
minLen = len;
start = i;
}
}
}
return start == -1 ? "" : s1.substr(start, minLen);
}
};- Time: ⏱️ O(m × n) - Preprocess and search
- Auxiliary Space: 💾 O(26m) - Next index arrays
- Efficient character lookup with preprocessing
- Good for multiple queries on same string
- Optimized for alphabet size consideration
- Use two pointers to expand and contract the window.
- Expand right pointer to include all characters of s2.
- Contract left pointer to minimize window size.
- Track minimum valid window throughout the process.
class Solution {
public:
string minWindow(string& s1, string& s2) {
int m = s1.size(), n = s2.size();
int start = -1, minLen = INT_MAX;
int i = 0, j = 0;
while (i < m) {
if (s1[i] == s2[j]) {
j++;
if (j == n) {
int end = i;
j--;
while (j >= 0) {
if (s1[i] == s2[j]) j--;
i--;
}
i++;
if (end - i + 1 < minLen) {
minLen = end - i + 1;
start = i;
}
j = 0;
}
}
i++;
}
return start == -1 ? "" : s1.substr(start, minLen);
}
};- Time: ⏱️ O(m × n) - Worst case backtracking
- Auxiliary Space: 💾 O(1) - Constant extra space
- Space efficient with minimal memory usage
- Natural sliding window pattern application
- Intuitive expand and contract logic
| 🚀 Approach | ⏱️ Time Complexity | 💾 Space Complexity | ✅ Pros | |
|---|---|---|---|---|
| 🏷️ Greedy Backtrack | 🟢 O(m × n) | 🟢 O(1) | 🚀 Optimal space usage | 🔧 Complex backtracking logic |
| 🔍 Dynamic Programming | 🟡 O(m × n) | 🟡 O(m × n) | 📖 Clear state transitions | 💾 High memory usage |
| 📊 Next Index Tracking | 🟡 O(m × n) | 🟡 O(26m) | 🎯 Fast character lookup | 💾 Extra preprocessing needed |
| 🔄 Two-Pointer Sliding | 🟡 O(m × n) | 🟢 O(1) | ⭐ Intuitive window logic | 🐌 May revisit positions |
| 🎯 Scenario | 🎖️ Recommended Approach | 🔥 Performance Rating |
|---|---|---|
| 🏅 Memory-constrained systems | 🥇 Greedy Backtrack | ★★★★★ |
| 📖 Learning DP patterns | 🥈 Dynamic Programming | ★★★★☆ |
| 🔧 Multiple queries on same text | 🥉 Next Index Tracking | ★★★★☆ |
| 🎯 Interview scenarios | 🏅 Two-Pointer Sliding | ★★★★★ |
class Solution {
public String minWindow(String s1, String s2) {
int m = s1.length(), n = s2.length();
int start = -1, minLen = Integer.MAX_VALUE;
for (int i = 0, j = 0; i < m; i++) {
if (s1.charAt(i) == s2.charAt(j)) j++;
if (j == n) {
int end = i;
j--;
while (j >= 0) {
if (s1.charAt(i) == s2.charAt(j)) j--;
i--;
}
i++;
j++;
if (end - i + 1 < minLen) {
minLen = end - i + 1;
start = i;
}
}
}
return start == -1 ? "" : s1.substring(start, start + minLen);
}
}class Solution:
def minWindow(self, s1, s2):
m, n = len(s1), len(s2)
start, minLen = -1, float('inf')
i = j = 0
while i < m:
if s1[i] == s2[j]:
j += 1
if j == n:
end = i
j -= 1
while j >= 0:
if s1[i] == s2[j]:
j -= 1
i -= 1
i += 1
j += 1
if end - i + 1 < minLen:
minLen = end - i + 1
start = i
i += 1
return "" if start == -1 else s1[start:start + minLen]For discussions, questions, or doubts related to this solution, feel free to connect on LinkedIn: 📬 Any Questions?. Let's make this learning journey more collaborative!
⭐ If you find this helpful, please give this repository a star! ⭐