leetcode 76. 最小覆盖子串(滑动窗口)
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”
说明:
如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
-
暴力解法
将字符串S划分成长度大于等于T的子串,一个个判断是否符合条件。
可以注意到,找到的字符串并没有严格按照字符串T的顺序,我们在判断是否符合条件的话就需要分别统计字符串中每个字符出现的次数,建立字符频数数组,然后逐个比较。
此方法做了很多没必要的比较,费时费力 -
滑动窗口
这个题目是典型的滑动窗口题。
我们先将窗口逐步覆盖S中第一个符合条件的字串,如图
然后再将左边界逐步右移至刚好使窗口符合条件的位置,此时为当前的最小字串,如图
为了寻找后面的最小字串,再将左边界右移一步,此时窗口不符合条件,如图
再将窗口右边界右移,直至符合条件,如图
继续将左边界右移直至刚好符合条件,比较此时的长度与之前找到的字串长度,取最小值
继续将左边界右移一步,此时窗口不符合条件,再将右边界右移直至符合条件,再对左边进行调整,最后的结果便是题目的输出
代码如下:
public String minWindow(String s, String t) {
int sLen = s.length();
int tLen = t.length();
if (sLen == 0 || tLen == 0 || sLen < tLen) {
return "";
}
char[] ss = s.toCharArray();//输入字符串
char[] tt = t.toCharArray();//目标字符串
int[] win = new int[128];//s的滑动窗口的字符频度记录表,字母z的ASCII为122,我们直接使用字母的ASCII作为下标
int[] tAll = new int[128];//目标字符串的字符频度记录表
for(char c:tt)
tAll[c]++;//直接将字符c转化为ASCII码
int right = 0;
int left = 0;
int begin = 0;
int end = 0;
int minlen = sLen+1;
int distance = 0;//记录滑动窗口中有多少个目标字符,若某个字符超过需要值我们不计入distance中
while(right < sLen)
{
char charRight = ss[right];//滑动窗口右边的字符
if(tAll[charRight]==0)//此字符不属于目标字符串中字符,滑动窗口右边右移
{
right++;
continue;
}
if(win[charRight]<tAll[charRight])//属于目标且滑动窗口刚好欠缺(超出了便不计入distance)
{
distance++;
}
win[charRight]++;
right++;
while(distance==tLen)//滑动窗口包含了全部目标字符串(甚至有的字符会超出需要)
{
if(right-left<minlen)//更新minlen,begin,end
{
minlen = right-left;
begin = left;
end = right;
}
char charLeft = ss[left];//滑动窗口左边的字符
if(tAll[charLeft]==0)//此字符不属于目标,滑动窗口左边右移
{
left++; continue;
}
if(win[charLeft]==tAll[charLeft])//此字符频度处于临界值,滑动窗口左边右移,此时滑动窗口不符合条件,等待滑动窗口右边右移再次符合条件
{
distance--;
}
win[charLeft]--;//就算滑动窗口左边存在某个字符超出需要,此时也可以向右滑动并更新minlen,begin,end
left++;
}
}
if(minlen==sLen+1) return "";
return s.substring(begin,end);
}
以上理解均基于leetcode的题解,详见:
leetcode 76. 最小覆盖子串 题解