NC28 最小覆盖子串

题意分析:

有字符串S和T,在S中找出包含T的所有字符的最短的子串

示例:S = "XDOYEZODEYXNZ",T = "XYZ"

S有多个子串包含XYZ,如XDOYEZ,YXNZ等等,其中YXNZ是最小的。

题解一(暴力):

只要枚举出包含T的所有子串,比较他们的长度,找出最小的那个。

bool check(string T, string S) {
    //检查S是否为t的子串
    map<char, int> m;
    for (int i = 0; i < S.size(); i++) {
        m[S[i]]++;
    }
    for (int i = 0; i < T.size(); i++) {
        if (m[T[i]] > 0)m[T[i]]--;
        else {
            return false;
        }
    }
    return true;
}

string minWindow(string S, string T) {
    if (S.size() < T.size()) return "";
    string ret = S;
    bool flag = false;
    for (int i = 0; i < S.size(); i++) {
        for (int j = T.size(); j <= S.size(); j++) {
            //检查字符串S的字串s[i,i+j]是否覆盖住了字符串T
            if (check(T, S.substr(i, j))) {
                if (ret.size() >= j) {
                    flag = true;
                    ret = S.substr(i, j);
                }
            }
        }
    }
    if (flag == true)
        return ret;
    else {
        return "";
    }
}

以上是一个超时代码。

时间复杂度:,S的所有子串长度一共有个,检查是否覆盖住需要O(n)遍历。

空间复杂度:

题解二:(滑动窗口)

我们在暴力求解的时候,check是否覆盖住有很多重复的检查。假设已经check了一次,下一次check只要检查与上一次不同的部分即可。

我们使用滑动窗口可以很好的比较出两次check字符的不同。

设置两个指针left,right。表示S的子串tmp可由left和right表示,当需要添加元素时候,就将right++,pop元素就left++。

我们用哈希表判断left到right是否完全包含T,动态维护窗口中所有字符以及个数。具体过程如下:

如果新加入的字符是被需要的(指在T里面),那么这个字符加入到窗口中,当窗口中的字符数目和被需要的数目相等时候,匹配度加一。right右移,这里匹配度是window里面的字符与need里面字符相等的数目。

如果新加入的字符不被需要(指不在T里面),right右移

当匹配度等于被需要的字符种类数,说明left-right覆盖到了T的所有字符,并且记录当前的left和right位置,然后就开始向右移动left

如果left位置的字符是被T所需要的,windo所统计的left字符要减一,当窗口中left处的字符数目小于need的字符数目,匹配度减一

如果left位置的字符不是被T所需要的,直接右移即可。
举个例子:
图片说明


图片说明

图片说明

图片说明

图片说明

图片说明

接下来的步骤就是像上面重复的一样
图片说明

最后返回结果。

string minWindow(string S, string T) {
    int left = 0, right = 0
    //表示窗口左右位置的指针
    int start = 0, minLen = INT_MAX;
    //start 表示最后结果字符串开始位置,minlen表示最后字符串长度,二者可以表示一个字符串
    unordered_map<char, int> need;
    //need的存放字符串T的所有字符统计
    unordered_map<char, int> window;
    //window 存放现有的窗口中出现在need中的字符统计

    for (auto i:T)need[i]++;
    int match = 0;
    //window与need的匹配度

    while (right < S.length()) {
        char c1 = S[right];
        if (need.count(c1)) {
            window[c1]++;
            if (window[c1] == need[c1])match++;
        }
        right++;
        while (match == need.size()) {
            //当匹配度等于need.size(),说明这段区间可以作为候选结果,更新ret
            if (right - left < minLen) {
                minLen = right - left;
                start = left;
            }
            char c2 = S[left];
            if (need.count(c2)) {
                window[c2]--;
                if (window[c2] < need[c2]) match--;
            }
            left++;
        }
    }
    return minLen == INT_MAX ? "" : S.substr(start, minLen);
}

时间复杂度:O(n)

空间复杂度:与T中的字符种类线性相关。