leetcode76. Minimum Window Substring(滑动窗口)(理解欠佳)

题目描述

Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).

Example:

Input: S = “ADOBECODEBANC”, T = “ABC”
Output: “BANC”
Note:

If there is no such window in S that covers all characters in T, return the empty string “”.
If there is such window, you are guaranteed that there will always be only one unique minimum window in S.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

用双指针 left 和 right 表示一个窗口。

right 向右移增大窗口,直到窗口包含了所有要求的字母。进行第二步。
记录此时的长度,left 向右移动,开始减少长度,每减少一次,就更新最小长度。直到当前窗口不包含所有字母,回到第 1 步。

思想有了,其实这里需要解决的问题只有一个,怎么来判断当前窗口包含了所有字母。

判断字符串相等,并且不要求顺序,之前已经用过很多次了,利用 HashMap,对于两个字符串 S = “ADOBECODEBANC”, T = “ABCB”,用 map 统计 T 的每个字母的出现次数,然后遍历 S,遇到相应的字母,就将相应字母的次数减 1,如果此时 map 中所有字母的次数都小于等于 0,那么此时的窗口一定包含了所有字母。

由于字符串中只有字母,我们其实可以不用 hashmap,可以直接用一个 int 数组,字母的 ascii 码值作为下标,保存每个字母的次数。

使用hashmap

public String minWindow(String s, String t) { 
    Map<Character, Integer> map = new HashMap<>();
    //遍历字符串 t,初始化每个字母的次数
    for (int i = 0; i < t.length(); i++) {
        char char_i = t.charAt(i);
        map.put(char_i, map.getOrDefault(char_i, 0) + 1);
    }
    int left = 0; //左指针
    int right = 0; //右指针
    int ans_left = 0; //保存最小窗口的左边界
    int ans_right = -1; //保存最小窗口的右边界
    int ans_len = Integer.MAX_VALUE; //当前最小窗口的长度
    //遍历字符串 s
    while (right < s.length()) {
        char char_right = s.charAt(right);
        //判断 map 中是否含有当前字母
        if (map.containsKey(char_right)) {
            //当前的字母次数减一
            map.put(char_right, map.get(char_right) - 1);
            //开始移动左指针,减小窗口
            while (match(map)) { //如果当前窗口包含所有字母,就进入循环
                //当前窗口大小
                int temp_len = right - left + 1;
                //如果当前窗口更小,则更新相应变量
                if (temp_len < ans_len) {
                    ans_left = left;
                    ans_right = right;
                    ans_len = temp_len;
                }
                //得到左指针的字母
                char key = s.charAt(left);
                //判断 map 中是否有当前字母
                if (map.containsKey(key)) {
                    //因为要把当前字母移除,所有相应次数要加 1
                    map.put(key, map.get(key) + 1);
                }
                left++; //左指针右移
            }
        }
        //右指针右移扩大窗口
        right++;
    }
    return s.substring(ans_left, ans_right+1);
}

//判断所有的 value 是否为 0
private boolean match(Map<Character, Integer> map) {
    for (Integer value : map.values()) {
        if (value > 0) {
            return false;
        }
    }
    return true;
}


使用int数组

public String minWindow(String s, String t) {
    int[] map = new int[128];
    // 遍历字符串 t,初始化每个字母的次数
    for (int i = 0; i < t.length(); i++) {
        char char_i = t.charAt(i);
        map[char_i]++;
    }
    int left = 0; // 左指针
    int right = 0; // 右指针
    int ans_left = 0; // 保存最小窗口的左边界
    int ans_right = -1; // 保存最小窗口的右边界
    int ans_len = Integer.MAX_VALUE; // 当前最小窗口的长度
    int count = t.length();
    // 遍历字符串 s
    while (right < s.length()) {
        char char_right = s.charAt(right);

        // 当前的字母次数减一
        map[char_right]--;
       
        //代表当前符合了一个字母
        if (map[char_right] >= 0) {
            count--;
        }
        // 开始移动左指针,减小窗口
        while (count == 0) { // 如果当前窗口包含所有字母,就进入循环
            // 当前窗口大小
            int temp_len = right - left + 1;
            // 如果当前窗口更小,则更新相应变量
            if (temp_len < ans_len) {
                ans_left = left;
                ans_right = right;
                ans_len = temp_len;
            }
            // 得到左指针的字母
            char key = s.charAt(left);
            // 因为要把当前字母移除,所有相应次数要加 1
            map[key]++;
            //此时的 map[key] 大于 0 了,表示缺少当前字母了,count++
            if (map[key] > 0) {
                count++;
            }
            left++; // 左指针右移
        }
        // 右指针右移扩大窗口
        right++;
    }
    return s.substring(ans_left, ans_right + 1);
}

另外写法

#include <iostream>
#include <cassert>

using namespace std;

/// Sliding Window
/// Time Complexity: O(n)
/// Space Complexity: O(1)
class Solution {
public:
    string minWindow(string s, string t) {

        int tFreq[256] = {0};
        for(int i = 0 ; i < t.size() ; i ++)
            tFreq[t[i]] ++;

        int sFreq[256] = {0};
        int sCnt = 0;

        int minLength = s.size() + 1;
        int startIndex = -1;

        int l = 0, r = -1;
        while(l < s.size()){

            if(r + 1 < s.size() && sCnt < t.size()){

                sFreq[s[r+1]] ++;
                if(sFreq[s[r+1]] <= tFreq[s[r+1]])
                    sCnt ++;
                r ++;
            }
            else{
                assert(sCnt <= t.size());
                if(sCnt == t.size() && r - l + 1 < minLength){
                    minLength = r - l + 1;
                    startIndex = l;
                }

                sFreq[s[l]] --;
                if(sFreq[s[l]] < tFreq[s[l]])
                    sCnt --;
                l ++;
            }
        }

        if( startIndex != -1 )
            return s.substr(startIndex, minLength);

        return "";
    }
};

int main() {

    cout << Solution().minWindow("ADOBECODEBANC", "ABC") << endl;
    // BANC

    cout << Solution().minWindow("a", "aa") << endl;
    // empty

    cout << Solution().minWindow("aa", "aa") << endl;
    // aa

    cout << Solution().minWindow("bba", "ab") << endl;
    // ba

    return 0;
}

优化

当T 的长度远远小于S,S中包括大量T中不存在的元素。

#include <iostream>
#include <cassert>
#include <unordered_set>
#include <vector>

using namespace std;

/// Sliding Window
/// Using filtered s, which remove all characters not in T
/// will be a good improvement when T is small and lots of character are not in S:)
///
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class Solution {
public:
    string minWindow(string s, string t) {

        unordered_set<char> t_set;
        int tFreq[256] = {0};
        for(char c: t){
            t_set.insert(c);
            tFreq[c] ++;
        }

        string filtered_s = "";
        vector<int> pos;
        for(int i = 0; i < s.size() ; i ++)
            if(t_set.find(s[i]) != t_set.end()){
                filtered_s += s[i];
                pos.push_back(i);
            }


        int sFreq[256] = {0};
        int sCnt = 0;

        int minLength = s.size() + 1;
        int startIndex = -1;

        int l = 0, r = -1;
        while(l < filtered_s.size()){

            if(r + 1 < filtered_s.size() && sCnt < t.size()){

                sFreq[filtered_s[r+1]] ++;
                if(sFreq[filtered_s[r+1]] <= tFreq[filtered_s[r+1]])
                    sCnt ++;
                r ++;
            }
            else{
                assert(sCnt <= t.size());
                if(sCnt == t.size() && pos[r] - pos[l] + 1 < minLength){
                    minLength = pos[r] - pos[l] + 1;
                    startIndex = pos[l];
                }

                sFreq[filtered_s[l]] --;
                if(sFreq[filtered_s[l]] < tFreq[filtered_s[l]])
                    sCnt --;
                l ++;
            }
        }

        if( startIndex != -1 )
            return s.substr(startIndex, minLength);

        return "";
    }
};


int main() {

    cout << Solution().minWindow("ADOBECODEBANC", "ABC") << endl;
    // BANC

    cout << Solution().minWindow("a", "aa") << endl;
    // empty

    cout << Solution().minWindow("aa", "aa") << endl;
    // aa

    cout << Solution().minWindow("bba", "ab") << endl;
    // ba

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值