leetcode 76 最小覆盖子串 滑动窗口
在串s中找出包含串t的所有字符的最小子串。
之前做了一些前缀和的题。想到用前缀和做:
统计第k个字符在第i个位置出现的个数:pre[i][k] .则可以用 前缀和遍历所有的子串组合。然后我们及时的减支
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 ""; }
但是有个问题,如果s很大,t很大。就会超出内存限制。。。
然后我用哈希表存储字符出现的个数,妄图遍历所有组合。不出意外的超时。
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 ""; }
看了题解,改成滑动窗口。仍然用哈希表记录字符出现的个数:
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中的字符,及它出现的次数。
- 用一个整数记录已经匹配到字符数。
这样就可以优雅的滑动窗口了:
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中所有字符串。此时我们收缩窗口。
所以 recode中对应left的字符要加一个,如果字符个数>0,则说明拿走了一个t串中的字符,所以match要减一个。
重复这个过程。直到right移动到s串外面。