题目链接
思路:滑动窗口
分析:s要包含t中所有字母,可以拆分为两个问题
1.s的子字符串中是否包含t中所有字母
2.如果包含,如何将这个子字符串缩短,并且要满足前提包含t中所有字母。
解决问题1:
要判断是否包含,并且这个判断的时间复杂度要常数级,那么我们可以将t中所有字母放到一个int数组中,因为包含大小写字母,一共26+26,最大长度为52,声明ts[52]。然后每次判断只需要去比对这个数组即可。
解决问题2:
定义一个左指针,指向0,右指针指向0,每次移动一下右指针,都判断一下是否包含了t中所有字母,如果在当前右边指针和左边指针包含了t中所有字母,那么开始移动左边指针,每次移动左边指针,也判断是否包含了t中所有字母,如果包含,继续左移,直到不包含,那么更新长度等于 右边指针位置 - 左边指针位置 + 1 + 1。
代码:
class Solution {
public String minWindow(String s, String t) {
//t的长度大于s的长度肯定不包含
if(t.length()>s.length()){
return "";
}
//因为有大小写字母 26 + 26 所以用长度为52的数组来记录
//words 用来记录滑动窗口中的字符的情况
int[] words = new int[52];
//ts用来记录t的字符情况
int[] ts = new int[52];
//i表示右边的指针
int i = 0;
//left表示左边的指针
int left = 0;
//遍历建立ts 并且顺带建立滑动窗口中字符的情况words
for(; i < t.length() ; i++){
//t的情况
if(t.charAt(i)>='A' && t.charAt(i)<='Z'){
ts[t.charAt(i)-'A']++;
}else{
ts[t.charAt(i)-'a'+26]++;
}
//滑动窗口的情况
if(s.charAt(i)>='A' && s.charAt(i)<='Z'){
words[s.charAt(i)-'A']++;
}else{
words[s.charAt(i)-'a'+26]++;
}
}
//长度
int res = Integer.MAX_VALUE;
//答案的左边
int minLeft = -1;
//答案的右边
int minRight = -1;
//遍历s
for(;i<s.length();i++){
//judgeII是判断words是否包含了t中所有的字符串
if(judgeII(words,ts)){
//如果包含了
while (true){
int index = 0;
//将左边第一个移除
if(s.charAt(left)>='A' && s.charAt(left)<='Z'){
index = s.charAt(left) - 'A';
words[index]--;
} else {
index = s.charAt(left) - 'a' + 26;
words[index]--;
}
left++;
//如果满足下列条件,说明出去了一个需要的字符,那么此时肯定已经不满足了words包含ts,否则就继续将左边的指针右移
if(words[index] < ts[index]){
break;
}
}
//判断是不是比现在的答案还短,更短的话,就更新
if(res > i-left+2){
res = i - left + 2;
minLeft = left-1;
minRight = i-1;
}
}
//将右边的字符加入words,也就是滑动窗口
if(s.charAt(i)>='A' && s.charAt(i)<='Z'){
words[s.charAt(i)-'A']++;
}else{
words[s.charAt(i)-'a'+26]++;
}
}
//下面就是判断最后一次,就是右边到达最后,此时特殊处理一下,再判断一下即可
if(judgeII(words,ts)){
while (judgeII(words,ts)){
if(s.charAt(left)>='A' && s.charAt(left)<='Z'){
words[s.charAt(left)-'A']--;
}else{
words[s.charAt(left)-'a'+26]--;
}
left++;
}
if(res > i-left+2){
res = i - left + 2;
minLeft = left-1;
minRight = i-1;
}
}
if(minLeft==-1){
return "";
}
return s.substring(minLeft,minRight+1);
}
//这个方法就是 时间复杂度O(1)的判断滑动窗口中是否包含t的方法
public boolean judgeII(int[] words,int[] ts){
for(int i = 0;i<ts.length;i++){
if(ts[i]>words[i]){
return false;
}
}
return true;
}
}
这个题目还可以继续优化,这里右边指针移动,我们就需要调用judgeII方法,这个方法判断需要遍历长度为52的数组,其实可以只要1次就可以解决的。
优化思路:记录t的长度,每次当滑动窗口中进入一个字符,并且该字符在加入滑动窗口后,对应的数组中的位置的值小于等于ts中该字符对应的位置的值,那么就说明进入了一个有效字符,也就是t中包含的字符,那么n–,当n=0的时候,说明words中就全部包含了t中的字符,这样就不用每次移动右边的指针就去遍历长度为52的数组了,而只需要比对一次就行。后面的思路修改也是如此,不过总的来说,两种思路都是O(n),不过一个是O(52*n),一个是真的O(n)