题目是下面
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”
给出字符串S和pattern T,找出包含T中所有字符的最短子字符串。
个人觉得这道题难点主要在各种细节。
思路进化:
待改进思路:
刚开始也是想到2 pointers,left和right。left先不动,right向右走,当left和right中间的字符串包含T中所有字符时,left和right都移向right的下一字符。
至于怎么判断是否包含T中所有字符,先用一个hashSet保存T中所有的char,然后再用一个tmp的hashSet保存,当size一样的时候就得到一个substring,这时再把tmp给clear掉。
改进点1:
这个方法有个问题,hashSet是不能保存重复char的,所以出现重复char的时候,如下例:
S = “a”, T = “aa”
这时塞进hashSet的char是一样的,会判断为是一样
所以需要用一个hashMap,key是T的当前char,Value是这个char出现的次数
改进点2:
left和right不能同时移到right的下一位,例如下面
S = “bbab”, T = “ab”
left = 0, right = 2时满足一个substring,这时left和right同时移到right的下一位,所以left和right都移到最右边的b,所以,最右边的最小字符串“ab”就被跳过去了,所以此方法不可行
解题思路:
根据上述改进点1,因为T中会有重复的字符,所以要记录下它的count,所以这里用一个hashMap,当然还有一种方法是定义一个char类型的数组,size128,包含所有char,然后数组内保存count,效果和hashMap一样,只不过O(1) 访问count。这里的方法用hashMap。
定义一个hashMap<Character, Integer> wordCount保存char和它出现的次数count
for (int i = 0; i < t.length(); i++) {
count = wordCount.getOrDefault(t.charAt(i), 0) + 1;
wordCount.put(t.charAt(i), count);
}
定义两个pointer,left和right。作一个sliding window。刚开始left和right都指向0,然后right右移。
问题1: 如何判断全部匹配了T中的字符?
当s.charAt(right)的char包含在T中时,取出char对应的count,并把count-1, 当count减到0时,算是匹配了一个字符,可以用一个matchCount变量记录,注意这个match对重复出现的字符也是unique的,当matchCount== wordCount.size()的时候就说明完全匹配了。
ch = s.charAt(right);
count = wordCount.get(ch);
if (count == null) {
right++;
continue;
}
wordCount.put(ch, count - 1);
//1 -> 0..
if (count == 1) {
matchCount ++;
}
例如:
S = “ADOBBCODEBANC”, T = “ABC”
这时left指向0,right指向5,substring是“ADOBBC”,长度是6,
这时用minLen变量记录下长度6,起始点index记录下这时的left
这时hashMap的内容是
<A, 0> <B, -1> <C, 0>
因为A出现一次,所以count-1=0
B出现2次,count-2=-1
C出现1次,count-1=0
B出现一次到达0后,出现第二次是仍然需要-1的,因为left右移时要用到。
这时substring已经包含了T中的所有字符,需要做的是缩减substring的长度,以便找到长度更短的substring,right向右移可以保证substring仍然有效,但是长度会变长,所以把left向右边移,以缩短长度。
当left指向的字符在T之内的时候,需要把s.charAt(left)对应的count++(因为前面说了count==0时表示字符匹配,移出去的时候不匹配了count就要+1),同时left++(右移)。
注意count由0变为1时,表示匹配的字符已经不在left和right构成的sliding window里了,matchCount需要-1
left指向的字符不在T之内的时候,直接将left++,所以这里不用if而用while来处理,处理什么,因为substring的length在减小,所以需要更新minLen和index
while (matchCount == wordCount.size()) {
if (right - left + 1 < minLen) {
minLen = right - left + 1;
index = left;
}
ch = s.charAt(left);
count = wordCount.get(ch);
if (count == null) {
left++;
continue;
}
//0 -> 1..
if (count == 0) {
matchCount--;
}
wordCount.put(ch, count + 1);
left++;
}
最后,当minLen没有得到更新,一直是最大值的时候,就说明S中没有包含T的字符串,返回“”
完整代码:
public String minWindow(String s, String t) {
if (s == null || t == null) {
return "";
}
if (s.length() == 0 || t.length() == 0 || s.length() < t.length()) {
return "";
}
HashMap<Character, Integer> wordCount = new HashMap<>();
Integer count = 0;
int minLen = Integer.MAX_VALUE;
int left = 0;
int right = 0;
char ch = 0;
int matchCount = 0;
int index = 0;
for (int i = 0; i < t.length(); i++) {
count = wordCount.getOrDefault(t.charAt(i), 0) + 1;
wordCount.put(t.charAt(i), count);
}
while (right < s.length()) {
ch = s.charAt(right);
count = wordCount.get(ch);
if (count == null) {
right++;
continue;
}
wordCount.put(ch, count - 1);
//1 -> 0..
if (count == 1) {
matchCount ++;
}
while (matchCount == wordCount.size()) {
if (right - left + 1 < minLen) {
minLen = right - left + 1;
index = left;
}
ch = s.charAt(left);
count = wordCount.get(ch);
if (count == null) {
left++;
continue;
}
//0 -> 1..
if (count == 0) {
matchCount--;
}
wordCount.put(ch, count + 1);
left++;
}
right++;
}
return minLen==Integer.MAX_VALUE ? "" : s.substring(index, index + minLen);
}