https://leetcode.com/problems/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).
For example,
S = "ADOBECODEBANC"
T = "ABC"
Minimum window is "BANC"
.
Note:
If there is no such window in S that covers all characters in T, return the emtpy string ""
.
If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.
解题思路:
这是比较难的一道题目,如果t中没有重复的字符,还算好做,但是有重复难度就增加不少了。
思路是使用之前用过的sliding window的方法,维护两个指针,分别指向这个窗口的开始和结束。Substring with Concatenation of All Words 和 Longest Substring Without Repeating Characters 都使用过这个方法。
因为T中的字符可能是重复的,我们就需要首先维护一个map,记录t中的字符和它们出现的次数。下面在遍历s的时候,可以维护一个同样的map,然后用它去和t的map比较。也可以直接在t的map上,将value--。如果value减到0,就代表该字符已经全部出现了,再出现就要<0。我们这里使用的是后一种。
当map所有的value都<=0的时候,代表该窗口已经涵盖了t中的所有字符。但是有一个问题,这个判断需要花费O(n)的时间,也就是需要对map做遍历。如何改进?下面的解法是维护了一个count的变量,如果value<0了,count就不++。也就是,仅仅记录当前窗口涵盖有效的t的字符数量,超过t中出现次数的字符的就不记录了。
具体遍历的时候,第一次遇见t中的字符,初始start值,当涵盖了所有的t的字符,该位置为end值。每次比较end - start + 1的大小,也就是width,记录最小的width和此时的start、end值。
那么,遇见第一次符合条件的窗口后,如何移动窗口,以遇见下一个?
思路是这样的,立刻将start往右移动一位,即start++。当然这里的count和map中value的值要相应变化。然后start不断右移,如果当前字符不在T中,当然右移。如果当前字符是T中有的,但是是多余的,也右移。直到正好遇见一个在T中,并且出现次数==T中次数的。因为这是剔除start后,窗口能缩小到的最小范围了,否则就要错过一个应该涵盖的字符!同时再次判断并更新minWidth值。
这样start停止,end继续往后遍历。
public class Solution { public String minWindow(String s, String t) { Map<Character, Integer> map = new HashMap<Character, Integer>(); for(int i = 0; i < t.length(); i++) { if(!map.containsKey(t.charAt(i))) { map.put(t.charAt(i), 1); } else { map.put(t.charAt(i), map.get(t.charAt(i)) + 1); } } int start = 0, end = 0, minStart = 0, minEnd = 0; int count = 0, minWidth = Integer.MAX_VALUE; for(int i = 0; i < s.length(); i++) { if(map.containsKey(s.charAt(i))) { if(count == 0) { start = i; } if(map.get(s.charAt(i)) > 0) { count++; } map.put(s.charAt(i), map.get(s.charAt(i)) - 1); } if(count == t.length()) { end = i; if(end - start + 1 < minWidth) { minWidth = end - start + 1; minStart = start; minEnd = i; } do { if(map.containsKey(s.charAt(start))) { map.put(s.charAt(start), map.get(s.charAt(start)) + 1); } if(map.containsKey(s.charAt(start)) && map.get(s.charAt(start)) > 0) { count--; } start++; } while(start <= i && (!map.containsKey(s.charAt(start)) || map.get(s.charAt(start)) < 0)); if(count == t.length()) { if(end - start + 1 < minWidth) { minWidth = end - start + 1; minStart = start; minEnd = i; } } } } if(minWidth == Integer.MAX_VALUE) { return ""; } return s.substring(minStart, minEnd + 1); } }
这种sliding window的题目面试中比较常见,因为可以将算法复杂度降低很多。但是老实讲,如果遇见难题的话,前面没做过,或者不熟悉,是比较难的。需要好好掌握。
update 2015/06/01:
二刷。先做完 Substring with Concatenation of All Words 再做这题,感觉真的是太像了。很自然的就能想到sliding window的方法,也做出来了。
但是必须注意,得出一个valid的区间后,左侧窗口不断缩减的条件是,先移去第一个,然后跳过所有不在t中的字符,同时也要跳过所有多余的字符,直到count能减去1。
同时,还要不断计算这里面存在的min。
public class Solution { public String minWindow(String s, String t) { Map<Character, Integer> wordMap = new HashMap<Character, Integer>(); for(int i = 0; i < t.length(); i++) { if(wordMap.containsKey(t.charAt(i))) { wordMap.put(t.charAt(i), wordMap.get(t.charAt(i)) + 1); } else { wordMap.put(t.charAt(i), 1); } } int start = -1, end = -1, count = 0; String min = ""; Map<Character, Integer> map = new HashMap<Character, Integer>(); for(int i = 0; i < s.length(); i++) { if(wordMap.containsKey(s.charAt(i))) { if(start == -1) { start = i; } if(map.containsKey(s.charAt(i))) { map.put(s.charAt(i), map.get(s.charAt(i)) + 1); } else { map.put(s.charAt(i), 1); } if(map.get(s.charAt(i)) <= wordMap.get(s.charAt(i))) { count++; } } if(count == t.length()) { end = i; if(min.length() == 0 || end - start + 1 < min.length()) { min = s.substring(start, end + 1); } do { // 每次start向后移动的时候,都要重新计算最小min if(count == t.length()) { if(min.length() == 0 || end - start + 1 < min.length()) { min = s.substring(start, end + 1); } } // 比较关键的操作,只有区间的字符不多于时,count才需要-- if(wordMap.containsKey(s.charAt(start))) { if(map.get(s.charAt(start)) <= wordMap.get(s.charAt(start))) { count--; } map.put(s.charAt(start), map.get(s.charAt(start)) - 1); } start++; } while(start <= end && (count >= t.length() || !wordMap.containsKey(s.charAt(start)))); } } return min; } }