76.最小覆盖子串

image-20200523170536726

滑动窗口+哈希

思路

  • 通过两个HashMap分别统计t中各字符的数量 滑动窗口中 含t中字符的数量

  • 滑动窗口不满足要求时,右指针向右移动 窗口不断扩张

  • 滑动窗口满足要求时,左指针向右移动 窗口收缩

  • 在窗口滑动过程中,同时记录并更新符合要求且宽度最小的 窗口信息

  • 通过 substring(ansL,ansR)返回

代码

/*
 * 134ms  7%
 */

//ori 统计字符串t 中 各个字符的数量
    Map<Character,Integer> ori=new HashMap<>();
    //cnt 滑动字符串中 各字符的计数器;
    Map<Character,Integer> cnt=new HashMap<>();
    public String minWindow(String s,String t){
        //字符串t 的长度
        int tLen=t.length();
        //遍历字符串t  同时通过ori 记录t中各个字符的数量
        for(int i=0;i<tLen;i++){
            char c=t.charAt(i);
            ori.put(c, ori.getOrDefault(c, 0)+1);
        }

        //l表示左指针  r表示右指针
        int l=0,r=-1;
        //len记录 滑动窗口的最小长度   ansL记录窗口的左端  ansR记录窗口的右端
        int len=Integer.MAX_VALUE,ansL=-1,ansR=-1;
        //sLen表示字符串s的长度
        int sLen=s.length();
        while (r<sLen){
            //移动右指针
            r++;
            //如果r指向的字符在 t中,滑动字符串计数器cnt 记录该字符 并更新其数量
            if(r<sLen&&ori.containsKey(s.charAt(r))){
                cnt.put(s.charAt(r), cnt.getOrDefault(s.charAt(r), 0)+1);
            }
            //先判断滑动字符串是否满足 t的要求,若满足,不断更新左指针(收缩窗口)
            while(check()&&l<=r){
                //若新窗口值比旧窗口还小 更新ansL ansR
                if(r-l+1<len){
                    len=r-l+1;
                    ansL=l;
                    ansR=l+len;
                }
                //收缩左指针  并更新cnt里的数量
                if(ori.containsKey(s.charAt(l))){
                    cnt.put(s.charAt(l), cnt.getOrDefault(s.charAt(l), 0)-1);
                }
                l++;
            }
        }
        //直接调用substring方法截取字符串
        return ansL==-1?"":s.substring(ansL, ansR);
    }

    public boolean check(){
        Iterator iter=ori.entrySet().iterator();
        while(iter.hasNext()){
            Map.Entry entry=(Map.Entry)iter.next();
            Character key=(Character) entry.getKey();
            Integer val=(Integer) entry.getValue();
            if(cnt.getOrDefault(key, 0)<val){
                return false;
            }
        }
        return true;
    }


优化

  • 仅用一个字符数组记录要匹配字符及其频数

代码

/**
 * 5ms  90%
 */
public String minWindow3(String s,String t){
        if (s == null || t == null || s.length() == 0 || t.length() == 0) return "";
        // 定义一个数字,用来记录字符串 t 中出现字符的频率,也就是窗口内需要匹配的字符和相应的频率
        int[] map = new int[128];
        for (char c : t.toCharArray()) {
            map[c]++;
        }
        int left = 0, right = 0;
        int match = 0;  // 匹配字符的个数
        int minLen = s.length() + 1;   // 最大的子串的长度
        // 子串的起始位置 子串结束的位置(如果不存在这样的子串的话,start,end 都是 0,s.substring 截取就是 “”
        int start = 0, end = 0;
        while (right < s.length()){
            char charRight = s.charAt(right); // 右边界的那个字符
            map[charRight]--;   // 可以理解为需要匹配的字符 charRight 减少了一个
            // 如果字符 charRight 在 t 中存在,那么经过这一次操作,只要个数大于等于 0,说明匹配了一个
            // 若字符 charRight 不在 t 中,那么 map[charRight] < 0, 不进行任何操作
            if (map[charRight] >= 0) match++;
            right++;  // 右边界右移,这样下面就变成了 [),方便计算窗口大小

            // 只要窗口内匹配的字符达到了要求,右边界固定,左边界收缩
            while (match == t.length()){
                int size = right - left;
                if (size < minLen){
                    minLen = size;
                    start = left;
                    end = right;
                }
                char charLeft = s.charAt(left);  // 左边的那个字符
                map[charLeft]++;  // 左边的字符要移出窗口
                // 不在 t 中出现的字符,移出窗口,最终能够达到的最大值 map[charLeft] = 0
                // 如果恰好移出了需要匹配的一个字符,那么这里 map[charLeft] > 0, 也就是还要匹配字符 charLeft,此时 match--
                if (map[charLeft] > 0) match--;
                left++;  // 左边界收缩
            }
        }
        return s.substring(start, end);

    }

参考链接:

官方题解

Kelly:Java 一个数组记录频数的滑动窗口

posted @ 2020-05-23 18:09  YH_Simon  阅读(196)  评论(0编辑  收藏  举报