给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”
说明:
如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
首先,根据题目,首先最应该想到的就是暴力求解,使用最单纯的暴力思路去把题目给解出来。
思路:
- 枚举输入字符串S的所有长度大于等于T的子串;
- 逐个判断这些子串中,哪些子串覆盖了字符串T的所有字符;
- 在枚举的过程中,记录符合条件的,长度最短的那个子串。
但是,如何判断S的子串包含了T中的所有字符?
参考做法:分别统计S的子串和T中每个字符出现的次数,然后逐个比对。
这样做答案肯定是对的,但是暴力解法的时间复杂度太高,达到了: O(|S|’^3+|T|),
这里|S|表示字符串S的长度,这里|T| 表示字符串T的长度;
空间复杂度有: 0(|S|+ |T|)。
所以,我们要想一个办法去逐步优化暴力解法——sliding window
采用双指针去找最小子串,定义winFreq
,和tFreq
两个字符频度数组去记录S,T 中的字符个数,
定义变量distance,
distance表示滑动窗口内部包含了T中字符的个数,窗口内单个字符个数等于T中对应的字符个数的时候不再增加
●当右边界向右滑动,且winFreq[s[right]] < tFreq[s[right]] 时,distance + 1
●当左边界向右滑动,且winFreq[s[left]] == tFreq[s[left]] 时,distance - 1
下面给出代码(代码里会有许多代码重用的地方以及还能优化的地方(例如可以不使用distance,而使用hashmap
去检查是否包含全部子串),导致了可读性降低,小伙伴们可以自行优化),代码里会有详细注释(如有错,欢迎指正):
public String minWindow(String s, String t) {
int sLen = s.length();
int tLen = t.length();
if (sLen == 0 || tLen == 0 || sLen < tLen) {
return "";
}
char[] charArrayS = s.toCharArray();
char[] charArrayT = t.toCharArray();
// 采用字符频度数组记录s子串是否包含t中所有字符
int[] winFreq = new int[128];
int[] tFreq = new int[128];
for (char c : charArrayT) { //记录t中字符的个数
tFreq[c]++;
}
// 滑动窗口内部包含多少T中的字符,对应字符频数超过不重复股计算
//distance表示滑动窗口内部包含了T中字符的个数,窗口内单个字符个数等于T中对应的字符个数的时候不再增加
int distance = 0;//利用distance计算winFreq中的子串是否包含了tFreq中的全部子串
int minLen = sLen + 1; //记录最小子串的长度
int begin = 0;//记录最小子串的起始址
int left = 0;//左右指针
int right = 0;
// [left, right)左开右闭使right-left恰好为长度
while (right < sLen) {//起初右指针先移动,找到恰好满足子串全部包含t中的字符
if (tFreq[charArrayS[right]] == 0) {//判断是否是t中的字符,不是则直接指针右移
right++;
continue;
}
if (winFreq[charArrayS[right]] < tFreq[charArrayS[right]]) {//判断子串中的t包含的字符是否超过t中的个数,使distance恰好等于t中的个数
distance++;
}
winFreq[charArrayS[right]]++;
right++;//右移
while (distance == tLen) { //当s子串恰好包含t中的全部字符,左指针开始移动
if (right - left < minLen) {//判断此时的字符串长度是否是最小,如果不是,则更新长度和起点
minLen = right - left;
begin = left;
}
if (tFreq[charArrayS[left]] == 0) {//同理判断左边界是否包含t中的字符
left++;
continue;
}
if (winFreq[charArrayS[left]] == tFreq[charArrayS[left]]) {//左边界字符恰好等于t中该字符的个数,则令distance-1,因为后面左指针会左移
distance--;
}
winFreq[charArrayS[left]]--;//个数减一
left++;//左移
}
}
if (minLen == sLen + 1) { //没有找到最小长度
return "";
}
return s.substring(begin, begin + minLen);
}