天天看点

leetcode 76 最小覆盖子串 滑动窗口

在串s中找出包含串t的所有字符的最小子串。

之前做了一些前缀和的题。想到用前缀和做:

统计第k个字符在第i个位置出现的个数:pre[i][k] .则可以用 前缀和遍历所有的子串组合。然后我们及时的减支

leetcode 76 最小覆盖子串 滑动窗口
leetcode 76 最小覆盖子串 滑动窗口

public String minWindow(String s, String t) {
char[] tCharArray = t.toCharArray();
        char[] sCharArray = s.toCharArray();
        int lenS = s.length();
        int lenT = t.length();
        HashMap<Character,Integer> indx = new HashMap<>();
        int[] count = new int[lenT];
        for(int i=0;i<lenT;i++){
            if(!indx.containsKey(tCharArray[i]))
                indx.put(tCharArray[i],i);
            count[indx.get(tCharArray[i])]++;

        }
        int charIndx = 0;
        int[][]precount = new int[lenS+1][lenT];
        for(int i=0;i<lenS;i++){
            for(int j=0;j<lenT;j++){
                charIndx = indx.get(tCharArray[j]);
                if(sCharArray[i]==tCharArray[j])
                    precount[i+1][charIndx]=precount[i][charIndx]+1;
                else
                    precount[i+1][charIndx]=precount[i][charIndx];
            }

        }
        for(int i=0;i<lenT;i++){
            charIndx=indx.get(tCharArray[i]);
            if(precount[lenS][charIndx]<count[charIndx]) return "";
        }
        int dis = Integer.MAX_VALUE;
        int minLenJ=-1;
        int minLenI=-1;
        for(int i=0;i<lenS;i++){
            for(int j=i+1;j<lenS+1;j++){
                if(dis<(j-i)) break;
                boolean containFlag = true;
                for(int k=0;k<lenT;k++){
                    charIndx=indx.get(tCharArray[k]);
                    if(precount[j][charIndx]-precount[i][charIndx]<count[charIndx]){
                        containFlag=false;
                        break;
                    }
                }
                if(containFlag){
                    if(dis>(j-i)){
                        minLenI=i;
                        minLenJ=j;
                        dis=j-i;
                    }

                }
            }
        }
        if(minLenI>-1){
            return s.substring(minLenI,minLenJ);
        }
        return "";

    }      

View Code

但是有个问题,如果s很大,t很大。就会超出内存限制。。。

然后我用哈希表存储字符出现的个数,妄图遍历所有组合。不出意外的超时。

leetcode 76 最小覆盖子串 滑动窗口
leetcode 76 最小覆盖子串 滑动窗口
public static boolean containsAll(HashMap<Character,Integer> src,HashMap<Character,Integer> subSrc){
        for(char c : src.keySet()){
            if(subSrc.getOrDefault(c,0)<src.get(c) ) return  false;
        }
        return true;
    }
    public static int[] shrink(char[] s,String t,int start,int end,HashMap<Character,Integer> src,HashMap<Character,Integer> subSrc){
        while (start<end){
            if(t.indexOf(s[start])!=-1){
                subSrc.put(s[start],subSrc.get(s[start])-1);
                if(containsAll(src,subSrc)){
                    start++;
                    continue;
                }else{
                    break;
                }
            }else{
                start++;
            }
        }
        return new int[]{start,end+1};
    }
    public static String minWindow(String s, String t) {
        char[] tCharArray = t.toCharArray();
        char[] sCharArray = s.toCharArray();
        int lenS = s.length();
        int lenT = t.length();
        if(lenT>lenS) return "";
        int dis = Integer.MAX_VALUE;
        int ans[] = new int[2];
        HashMap<Character,Integer> count = new HashMap<>();
        for(int i=0;i<lenT;i++){
            count.put(tCharArray[i],count.getOrDefault(tCharArray[i],0)+1);
        }
        HashMap<Character,Integer> subCount = new HashMap<>();
        for(int i=0;i<lenS;i++){
            if(lenS-i<lenT) break;
            if(t.indexOf(sCharArray[i])!=-1){
                subCount.clear();
                for(int j=i;j<lenS;j++){
                    if(t.indexOf(sCharArray[j])!=-1){
                        subCount.put(sCharArray[j],subCount.getOrDefault(sCharArray[j],0)+1);
                        if(containsAll(count,subCount)){
                            int[] tmp = shrink(sCharArray,t,i,j,count,subCount);
                            if((tmp[1]-tmp[0])<dis){
                                ans[0]=tmp[0];
                                ans[1]=tmp[1];
                                dis=tmp[1]-tmp[0];
                            }

                        }
                    }
                }
            }
        }
        if(dis<Integer.MAX_VALUE) return s.substring(ans[0],ans[1]);
        return "";

    }      

看了题解,改成滑动窗口。仍然用哈希表记录字符出现的个数:

leetcode 76 最小覆盖子串 滑动窗口
leetcode 76 最小覆盖子串 滑动窗口
public static boolean containsAll(HashMap<Character,Integer> src,HashMap<Character,Integer> subSrc){
        for(char c : src.keySet()){
            if(subSrc.getOrDefault(c,0)<src.get(c) ) return  false;
        }
        return true;
    }
    public static int[] shrink(char[] s,String t,int start,int end,HashMap<Character,Integer> src,HashMap<Character,Integer> subSrc){
        while (start<end){
            if(t.indexOf(s[start])!=-1){
                subSrc.put(s[start],subSrc.get(s[start])-1);
                if(containsAll(src,subSrc)){
                    start++;
                    continue;
                }else{
                    break;
                }
            }else{
                start++;
            }
        }
        return new int[]{start,end+1};
    }
    public static String minWindow(String s, String t) {
        char[] tCharArray = t.toCharArray();
        char[] sCharArray = s.toCharArray();
        int lenS = s.length();
        int lenT = t.length();
        if(lenT>lenS) return "";
        int dis = Integer.MAX_VALUE;
        int ans[] = new int[2];
        HashMap<Character,Integer> count = new HashMap<>();
        for(int i=0;i<lenT;i++){
            count.put(tCharArray[i],count.getOrDefault(tCharArray[i],0)+1);
        }
        HashMap<Character,Integer> subCount = new HashMap<>();
        int start = 0;
        int end = start;
        while (end<lenS){
            while(end<lenS){
                if(t.indexOf(sCharArray[end])!=-1){
                    subCount.put(sCharArray[end],subCount.getOrDefault(sCharArray[end],0)+1);
                    if(containsAll(count,subCount)) break;
                }
                end++;
            }
            while (start<=end&&end<lenS){
                if(t.indexOf(sCharArray[start])!=-1){
                    subCount.put(sCharArray[start],subCount.get(sCharArray[start])-1);
                    if(!containsAll(count,subCount)){
                        if(end-start<dis){
                            dis=end-start;
                            ans[0]=start;
                            ans[1]=end+1;
                        }
                        start++;
                        end++;
                        break;
                    }
                }
                start++;
            }
        }
        if(dis<Integer.MAX_VALUE) return s.substring(ans[0],ans[1]);
        return "";      

一点都不优雅。继续看题解。发现优雅的解法。

  • t中的字符,走不出 ascii码,所以我们可以用一个 128长度的数组记录所有t中的字符,及它出现的次数。
  • 用一个整数记录已经匹配到字符数。

这样就可以优雅的滑动窗口了:

leetcode 76 最小覆盖子串 滑动窗口
leetcode 76 最小覆盖子串 滑动窗口
public static String minWindow2(String s, String t) {
        int lenS = s.length();
        int lenT = t.length();
        if(lenT==0||lenS<lenT)return "";
        char[] sC = s.toCharArray();
        char[] tC = t.toCharArray();
        int[] record = new int[128];
        for(int i=0;i<lenT;i++){
            record[tC[i]]++;

        }
        int left=0,right=0;
        int match = 0;
        int[] ans = new int[2];
        int dis = Integer.MAX_VALUE;
        while (right<lenS){
            record[sC[right]]--;
            if(record[sC[right]]>=0) match++;
            right++;
            while (match==lenT){
                if(dis>right-left){
                    dis=right-left;
                    ans[0]=left;
                    ans[1]=right;
                }
                record[sC[left]]++;
                if(record[sC[left]]>0) match--;
                left++;
            }
        }
        return s.substring(ans[0],ans[1]);

    }      

重点看一下滑动窗口这里:

     int left=0,right=0;
     int match = 0;
        int[] ans = new int[2];
        int dis = Integer.MAX_VALUE;
        while (right<lenS){
            record[sC[right]]--;
            if(record[sC[right]]>=0) match++;
            right++;
            while (match==lenT){
                if(dis>right-left){
                    dis=right-left;
                    ans[0]=left;
                    ans[1]=right;
                }
                record[sC[left]]++;
                if(record[sC[left]]>0) match--;
                left++;
            }
        }      

record记录了t串中有多少个字符没有在窗口中。初始为t串中的所有字符。

当窗口滑动时。我们把right指向的字符从record中减掉一个。这个时候,如果record上的字符个数>=0,说明匹配到一个t中的字符。match++。

当match==lenT的时候,说明从letf到right的窗口中包含了t中所有字符串。此时我们收缩窗口。