76. 最小覆盖子串(Minimum Window Substring)
题目描述:
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:
如果 S 中不存这样的子串,则返回空字符串 ""。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
解题思路:
这道题用双指针滑动窗口完成。大体思路是记录一个区间,当区间符合要求时,就滑动左边界收缩区间;当区间不符合要求时,就滑动右边界放大区间。每当区间符合要求时,就比较此时区间的值是否是最小值,如果是就更新最小值。
在这道题中,判断区间是否符合要求稍微复杂一些,需要使用两个unordered_map,一个保存需要满足的字符,一个保存当前区间中的字符。还需要一个整数变量记录符合要求的字符数,从而避免每次都要遍历两个map来判断是否符合条件。过程如下:
只要右边界没有到s的最大长度,就一直循环。循环中每次把右边界的值加入到区间中,假设加入的字符用ch表示,那么如果ch不在need_map中时,表示该字符没用,就继续循环。如果ch在need_map中,就令window_map中该字符对应的值加1,如果该值等于need_map中字符对应的值,就表示这个字符已经满足了,记录字符个数的变量matched_num加1。接着判断matched_num是否等于need_map中字符的数量,如果等于就表示该区间符合要求,就要收缩左边界。如果左边界的字符ch不在need_map中,同样说明该字符没用,继续收缩左边界。直到左边界字符ch在need_map中时,将window_map中ch对应的值减一,如果该值小于need_map中字符对应的值,就将matched_num减一,此时会跳出收缩左边界循环。
注意每次区间符合条件时,都要记录此时的区间长度和起始位置,最后返回区间长度最小的子串。
代码如下:
class Solution { public: string minWindow(string s, string t) { int l = 0, r = 0, match_num = 0; unordered_map<char, int> needs, windows; int len = s.size() + 1, begin_pos; for (auto && ch : t) ++needs[ch]; while (r != s.size()) { if (needs.count(s[r]) == 0) { ++r; continue; } if (++windows[s[r]] == needs[s[r]]) ++match_num; while (match_num == needs.size()) { if (len > r - l + 1) { len = r - l + 1; begin_pos = l; } if (needs.count(s[l]) == 0) { ++l; continue; } if (--windows[s[l]] < needs[s[l]]) --match_num; ++l; } ++r; } if (len == s.size() + 1) return ""; else return s.substr(begin_pos, len); } };