1、题目描述
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
2、解答思路
本题的主要思路是枚举 s 子串的右指针 right,如果子串覆盖字符串 t,就不断移动左指针 left 使得窗口达到最小值,直到不覆盖为止。在移动过程中更新最短子串的左右端点。我们可以想到解题思路,但是在O(1)的时间复杂度下判断子串是否覆盖字符串 t 是个难点。这里使用less变量,即子串中有less种字母的个数小于t中字母的个数。比如子串是 “ab”, t 是 “aab”,那么子串中还有一种字母的个数小于 t 中字母的个数,即 ‘a’ 的个数。
- 若less>0,说明子串仍未完全覆盖字符串 t,右指针继续往右移动,即窗口继续扩大。
- 若less=0,说明子串完全覆盖字符串 t,t 中所有字母的个数都小于等于子串中对应字母的个数。
那么如何判断 less 的增减呢,这里使用 int cnt[]数组,以字母的ascii码为索引,首先存储字符串 t 的各个字母个数,右指针 right 往后遍历字符串 s 的时候 cnt[ch]的数量减一,如果 cnt[ch]==0,那么说明当前窗口中的 ch 数量等于字符串 t 中 ch 数量,种类数 less 减一;在左指针往右遍历的时候,如果 cnt[ch]==0,说明往右遍历之后窗口中 cnt[ch] 出现的次数会小于 t 中 cnt[ch] 出现的次数,那么less++。
class Solution {
public String minWindow(String S, String t) {
// 将字符串转换为数组,方便用[索引]取元素
char[] s = S.toCharArray();
int m = s.length;
// 左右指针
int right = 0, left = 0;
// 最终结果的左右指针(由于之后涉及到ansRight-ansLeft的比较,因此这里分别取m和-1)
int ansRight = m, ansLeft = -1;
// 由于最大的ascii码值为127,因此这里的数组长度为128
int[] cnt = new int[128];
// 子串中有less种字母的个数小于t中字母的个数
int less = 0;
// 当前子串为空,遍历t中字符并得到字母个数(cnt[])以及不同字母的个数(less)
for(char ch: t.toCharArray()){
// cnt[ch]==0说明新的字母出现
if(cnt[ch]==0)
less++;
cnt[ch]++;
}
// 右指针依次向右遍历
for(right = 0; right < m; right++){
char ch = s[right];
// 无需判断t中是否包含ch
// 若t中不包含ch,则值为负数,不影响后续结果
cnt[ch]--;
// cnt[ch] == 0说明当前窗口中的ch数量==t中ch数量,种类数减一
if(cnt[ch] == 0){
less--;
}
while(less==0){
// 更新最小窗口的左右指针
if(right-left < ansRight-ansLeft){
ansRight = right;
ansLeft = left;
}
// 窗口的左边界向右移动,得到尽可能小的符合条件的子串
ch = s[left++];
cnt[ch]++;
// 左指针右移之后cnt[ch]>0,说明窗口中x出现的次数小于t中x出现的次数,那么less++
if(cnt[ch] > 0){
less++;
}
}
}
return ansLeft < 0 ? "" : S.substring(ansLeft, ansRight + 1);
}
}
- 注意字符串s的字母大小写任意,因此不宜使用s[ch-‘a’]的方式,直接将字母的ascii码作为索引会更方便。
- 时间复杂度:O(m+n+∣Σ∣),其中 m 为 s 的长度,n 为 t 的长度,∣Σ∣=128。
- 空间复杂度:O(∣Σ∣),其中∣Σ∣=128。