一、题目描述
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
- 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
- 如果 s 中存在这样的子串,我们保证它是唯一的答案。
二、解题思路
使用滑动窗口,s="ABAACBAB"
,T="ABC"
,定义两个指针来遍历,使用变量minLen来记录窗口长度:
第一步:right不停的向后移动,直到窗口中的元素涵盖了所有的T中的元素,此时minLen=5
第二步:尝试缩小窗口,先将left向后移动一位,移动之后判断此时窗口中是否还包含T中的所有元素,如果包含则更新minLen
,此时minLen=4
:
left继续右移,发现此时的窗口不能再继续包含所有T中的元素,所以需要扩大窗口了,right向后移动,之后继续重复第一、二步即可,每次minLen
更新是当前窗口中包含所有T中的元素,且当前窗口长度小于minLen
最终结果如下,right遍历完整个字符串,此时minLen=3
,返回输出子串为ACB
或者CBA
都可以.
三、代码演示
class Solution {
public String minWindow(String s, String t) {
//统计字符串t中每个字符出现的次数:26个大写,26个小写字母,它们的ASCII码之间还有8个其它字符
int[] cntT = new int[60];
//统计t中有多少个不同的字符
int uniqueCharInt = 0;
for (char c : t.toCharArray()){
if (cntT[c-'A']==0){
uniqueCharInt++;
}
cntT[c-'A']++;
}
int left=0, right=0;
//窗口中每个字符出现的次数
int[] windowCntS = new int[60];
//记录当前窗口中字符出现的次数和t中字符出现次数相等的字符数
int matchedChars =0;
//结果集:-1表示字串最小长度,两个0分别表示left和right边界
int[] ans = {-1,0,0};
while (right<s.length()){
char rightChar = s.charAt(right);
//计算right指针指向元素的索引
int rightCharIndex = rightChar-'A';
//当前窗口中rightCharIndex所指元素出现过,所以次数加一次
windowCntS[rightCharIndex]++;
//如果窗口里某个字符出现的次数等于了这个字符在t中出现的次数,说明这个字符匹配上了
if (windowCntS[rightCharIndex]==cntT[rightCharIndex]){
matchedChars++;
}
//如果窗口中出现了所有t中的元素,说明找到了符合条件的字串
while (left<=right && matchedChars==uniqueCharInt){
//尝试缩减窗口,因为找的是最短符合条件的字串
if (ans[0]==-1 || right-left+1 < ans[0]){
ans[0] = right-left+1;
ans[1] = left;
ans[2] = right;
}
//要缩短窗口,left右移,窗口中当前left所指元素要移除掉
char leftChar = s.charAt(left);
int leftCharIndex = leftChar-'A';
windowCntS[leftCharIndex]--;
//如果窗口中当前left所指字符出现的次数小于它在t里面出现的次数,说明该元素不匹配
if (windowCntS[leftCharIndex] < cntT[leftCharIndex]){
//已匹配的字符总数减一
matchedChars--;
}
left++;
}
right++;
}
return ans[0] == -1 ? "" : s.substring(ans[1],ans[2]+1);
}
}