题目:给你一个字符串 S、一个字符串 T 。请你设计一种算法,可以在 O(n) 的时间复杂度内,从字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入:S = "ADOBECODEBANC", T = "ABC"
输出:"BANC"
提示:
- 如果 S 中不存这样的子串,则返回空字符串 “”。
- 如果 S 中存在这样的子串,保证它是唯一的答案。
题解:
滑动窗口思想
用i,j来表示当前滑动窗口的左边界和右边界,当当前的滑动窗口包含T的时候,记录长度,并在这些长度中取最小长度即可
具体步骤
- 通过增大j的值,使窗口向右扩大直到包含T的所有字符
- 通过增大i的值,使窗口左侧不断缩小直到窗口最左侧为T中的某个字符(也就是不能再缩小了)
- 这时i,j之间的字符串就对应了一个解,和之前保存的长度比较,选择最小值
- 使左侧窗口右移一位(i++),这时滑动窗口字符串不满足条件了,返回执行1
在整个步骤执行过程中,最难的就是如何确定右边界(j指针)扩大到刚好包含了T中所有字符,左边界(i指针)缩小到刚好不能再缩小
在这里我通过need数组来记录,need数组的下标表示字符的ASCII码值,而对应的值为字符出现次数,首先我们要用T字符串来初始化这个数组,在滑动窗口每滑动一次,我们都要去维护这个need。指针每此移动,我们都让当前指针指向的元素对应need中数值减一。
通过这个need便解决了这两个问题,
右边界的停止条件:need中下标为T中字符的所有对应值都为0,在这里我们可以用count来等效,也就是用count来记录总个数(T的长度),当count==0时也就代表need中相应字符下标对应的值都为0了。
左边界的停止条件:在右边界停止以后,我们要缩小左边界,也就是i++,可以想到对于多余的元素,在need中对应的值是都为负数的(因为除了初始化的字符外,其余的默认都为0,一但遇到了,减一后就为负数了),所以如果判定当前字符在need中的值为负数,那左边界就要右移缩小,直到等于0,就不能再缩小。
代码:
class Solution {
public String minWindow(String s, String t) {
if(s==null||s.length()==0||t==null||t.length()==0)
return "";
int[] need=new int[128];
int l=0,r=0;//左边界和右边界初始化
int count=t.length();//也就是总数量
int start=0;//记录得到的字符串的左边界
int size=Integer.MAX_VALUE;//记录得到的字符串长度
for(int i=0;i<t.length();i++)//初始化need数组
need[t.charAt(i)]++;
while(r<s.length()){//右移开始
char c=s.charAt(r);
if(need[c]>0)//找到有效字符,总数量减一
count--;
need[c]--;//对应元素数量减一
if(count==0){//此时停止右边界继续右移
//开始缩小左边界
while(need[s.charAt(l)]<0){
need[s.charAt(l)]++;//释放左边移动出窗口的字符
l++;//指针右移
}
if(r-l+1<size){//更新最优解
start=l;//记录字符串开始下标
size=r-l+1;//记录长度
}
//左指针右移,使窗口不满足条件重新开始循环
need[s.charAt(l)]++;
l++;
count++;
}
r++;
}
return size==Integer.MAX_VALUE?"":s.substring(start,start+size);
}
}
小白一枚,还望大佬们多多指正。