Minimum Window Substring

LeetCode problem 76. Minimum Window Substring.

## Problem description

Given two strings `s` and `t` of lengths `m` and `n` respectively, return *the **minimum window substring** of* `s` *such that every character in* `t` *(**including duplicates**) is included in the window. If there is no such substring, return the empty string* `""`*.*

The testcases will be generated such that the answer is **unique**.

A **substring** is a contiguous sequence of characters within the string.

Example 1
**Input:** s = "ADOBECODEBANC", t = "ABC"
**Output:** "BANC"
**Explanation:** The minimum window substring "BANC" includes 'A', 'B', and 'C' from string t.
Example 2
**Input:** s = "a", t = "a"
**Output:** "a"
**Explanation:** The entire string s is the minimum window.
Example 3
**Input:** s = "a", t = "aa"
**Output:** ""
**Explanation:** Both 'a's from t must be included in the window. Since the largest window of s only has one 'a', return empty string.
Constraints
* `m == s.length`
* `n == t.length`
* `1 <= m, n <= 105`
* `s` and `t` consist of uppercase and lowercase English letters.

Follow up
Could you find an algorithm that runs in `O(m + n)` time?
## Problem analysis

This problem requires us to consider duplicated characters in the target string *t*. That is, for example, if `t = "abac"`, then the window we find must have two `'a'`'s, one `'b'`, and one `'c'`.

So we can use a hash map to store the counts of each character in *t*. Then, when moving the window in *s*, we are able to check whether the window contains all the target characters in *t*.

The hash map is initialized as follows:

1. For each character in *t*, add 1 to the count of that character in the hash map. **After initialization, this map indicates *how many of each characters are yet to be matched*.**
2. Initialize the `balance` variable to be the total number of characters in the hash map. **`balance` will indicate *how many characters are yet to be matched* in the sliding window.**

Then we can start moving the sliding window. We initialize the following variables: `left = 0`, `right = -1`, `start = -1`, `minLen = INT_MAX`.

Then we use a `while` loop to move the right pointer to the right. At the *beginning* of each iteration, we make a decision based on whether `balance` is positive or 0.

* **If `balance == 0`, this means that the current window `s[left..right]` is a valid window.** We do the following:
	1. Compute window length: `len = right - left + 1`.
	2. Update `minLen` and `start`, if applicable, i.e. `len < minLen`.
	3. Pop the original left character in the window, update the `counts` map and the `balance` variable, and move the `left` pointer to the right by 1 step.
* **If `balance > 0`, this means that there are some characters yet to be matched.** We do the following:
	1. Move the `right` pointer to the right by 1 step, and add the right character into the window. Update the `counts` map and the `balance` variable.
	2. If we have already found a valid window before (i.e. `start > 0`), we pop the original left character in the window, update the `counts` map and the `balance` variable, and move the `left` pointer to the right by 1 step. Otherwise we skip this step.

Rules to update the `counts` map and the `balance` variable:

* Only when adding or removing characters in *t* would we update the `counts` map and the `balance` variable.
* For each character `ch`:
	+ If `counts[ch] > 0` this means that we still need to match `counts[ch]` copies of character `ch`.
	+ If `counts[ch] == 0` this means the sliding window in *s* has fully matched the character `ch` in *t*.
	+ If `counts[ch] < 0` this means the sliding window in *s* has overmatched the character `ch` in *t*, and there are `-counts[ch]` extra copies of character `ch` in the window. For example, if we only need to match 2 copies of `'a'`, but the window has 3 copies of `'a'`, then `counts['a'] = -2`.
* **Adding a right character `s[right]` to the window:**
	1. `--counts[s[right]]`.
	2. If `counts[s[right]] >= 0`, this means that we have one less character to match in the window, so we subtract 1 from `balance`. Otherwise the character `s[right]` is overmatched (e.g. having 3 copies of character `'a'` while we only need 2), so `balance` is not changed.
* **Removing a left character `s[left]` from the window:**
	1. `++counts[s[left]]`.
	2. If `counts[s[left]] >= 1`, this means that we have popped one needed character from the window, so we add 1 to `balance`. Otherwise the character `s[left]` is overmatched (e.g. having 3 copies of character `'a'` while we only need 2), so `balance` is not changed.

Finally the result will be:

* If `start == -1`, i.e. no valid window is ever found, we return `""`.
* Otherwise we return `s.substr(start, minLen)`.

### Working example

Input
`s = "ADOBECODEBANC", t = "ABC"`
Procedure

1. Initialize the hash map: `{'A': 1, 'B': 1, 'C': 1}`.
2. Start with `left = 0`, `right = -1`, `start = -1`, `minLen = INT_MAX`.
3. Start the `while` loop, with the condition that `right < len`. Here are the key events during the iterations:
	1. Move `right` to 5, yielding a valid window `"ADOBEC"` with length 6 (`start = 0`, `minLen = 6`). Then move `left` to 1.
	2. Now after moving `left` we don't have a valid window any more. So move `left` and `right` together, in order to consider the windows with length 5. Stop when `right` arrives at 12. At this moment `left` arrives at 8. Now the window `"EBANC"` is a valid window with shorter length, so set `start = 8` and `minLen = 5`. Move `left` to 9.
	3. Now the window `"BANC"` is another valid window that is shorter. As a result set `start = 9` and `minLen = 4`. Move `left` to 10.
	4. Now we no longer have a valid sliding window, so move `right` to 13. Since 13 is already past the maximum possible index, we exit the loop.
4. The final result is `"BANC"`.

Output
`"BANC"`
### Complexity analysis

Let *m*, *n* be the length of *s* and *t* respectively.

Space complexity
We need to use O(n) space to store the counts in a hash map. So the space complexity is **O(n)**.
Time complexity
We have the following steps:
1. Initialize the hash map. The time complexity is O(n).
2. Move the sliding window. For each iteration the computation time is constant. So this part has O(m) time complexity.
3. Generate output. The time complexity is O(m).

So the total time complexity is **O(m + n)**.
## Solution

### C++ solution

```
class Solution {
private:
 static bool contains(const std::unordered\_map<char, int>& counts, char ch) {
 return counts.find(ch) != counts.end();
 }

public:
 std::string minWindow(std::string& s, std::string& t) {
 const int m = s.length(), n = t.length();
 if (m < n) {
 // s is too short to contain t
 return "";
 }

 // initialize counts
 std::unordered\_map<char, int> counts;
 int balance = 0;
 for (const auto& ch: t) {
 ++counts[ch];
 ++balance;
 }

 // use sliding window
 int left = 0, right = -1, start = -1, minLen = INT\_MAX;
 while (right < m) {
 // make a decision of whether to move left pointer or right pointer
 // based on whether balance is positive or 0
 if (balance == 0) {
 // s[left..right] is a valid window
 // update start and minLen
 int len = right - left + 1;
 if (len < minLen) {
 minLen = len;
 start = left;
 }

 // pop left character, update counts and balance
 if (contains(counts, s[left]) && (++counts[s[left]] >= 1)) {
 ++balance;
 }
 ++left;
 }
 else {
 // s[left..right] is not a valid window
 if (++right >= m) {
 break;
 }

 // add right character, update counts and balance
 if (contains(counts, s[right]) && (--counts[s[right]] >= 0)) {
 --balance;
 }
 // pop left character, update counts and balance
 // only if a valid window has been found before
 if (start > -1) {
 if (contains(counts, s[left]) && (++counts[s[left]] >= 1)) {
 ++balance;
 }
 ++left;
 }
 }
 }

 return (start > -1) ? s.substr(start, minLen) : "";
 }
};

```

### Java solution

```
class Solution {
 public String minWindow(String s, String t) {
 final int m = s.length(), n = t.length();
 if (m < n) {
 // s is too short to contain t
 return "";
 }

 // initialize counts
 Map<Character, Integer> counts = new HashMap<>();
 int balance = 0;
 for (int i = 0; i < n; ++i) {
 char ch = t.charAt(i);
 int count = counts.getOrDefault(ch, 0);
 counts.put(ch, ++count);

 ++balance;
 }

 // use sliding window
 int left = 0, right = -1, start = -1, minLen = Integer.MAX\_VALUE;
 while (right < m) {
 // make a decision of whether to move left pointer or right pointer
 // based on whether balance is positive or 0
 if (balance == 0) {
 // s[left..right] is a valid window
 // update start and minLen
 int len = right - left + 1;
 if (len < minLen) {
 minLen = len;
 start = left;
 }

 // pop left character, update counts and balance
 char leftCh = s.charAt(left);
 if (counts.containsKey(leftCh)) {
 int count = counts.get(leftCh);
 counts.put(leftCh, ++count);
 if (count >= 1) {
 ++balance;
 }
 }
 ++left;
 }
 else {
 // s[left..right] is not a valid window
 if (++right >= m) {
 break;
 }

 char leftCh = s.charAt(left), rightCh = s.charAt(right);

 // add right character, update counts and balance
 if (counts.containsKey(rightCh)) {
 int count = counts.get(rightCh);
 counts.put(rightCh, --count);
 if (count >= 0) {
 --balance;
 }
 }
 // pop left character, update counts and balance
 // only if a valid window has been found before
 if (start > -1) {
 if (counts.containsKey(leftCh)) {
 int count = counts.get(leftCh);
 counts.put(leftCh, ++count);
 if (count >= 1) {
 ++balance;
 }
 }
 ++left;
 }
 }
 }

 return (start > -1) ? s.substring(start, start + minLen) : "";
 }
}

```