最小子串覆盖 滑动窗口

最小覆盖子串

今天做题的时候了解到了滑动窗口,主要是在数组上应用,它能把 O(n2) 的暴力解法优化到 O(n) ,在对数组的操作中非常好用

不了解滑动窗口的建议先去学一下再来解决这道题

题目描述

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意:

给你一个字符串 s 和一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

如果采用暴力解法的话

//伪代码
for(int i = 0; i < s.length(); i++) {
    for(int j = 0; j < s.length(); j++) {
        if(s[i~j] 包含 子串 t) {
            minlen = minlen < i - j + 1 ? minlen : i - j + 1;
        }
    }
}

算法复杂度为 O(n2),对于大于1e5的复杂度,是会超时的,所以要用到下面滑动窗口的方法来解决

用i,j表示滑动窗口的左边界和右边界,通过改变i,j来改变滑动窗口的大小,可以想象成一个大小可变的窗口在字符串上移动,当这个窗口包含的元素满足条件,即包含字符串t的所有元素,记录下这个滑动窗口的长度,这些长度中的最小值就是要求的结果。

初始化 i = j = 0

① i不动, j不断向后移动,直到窗口中包含 t 中的所有元素(注意重复元素的数量)

② 从左边不断缩小窗口,即 i 向后移动,直到 i 如果再向后移动窗口内就不再包含 t 中所有元素,记录此时窗口长度

③ 让 i 再后移一位,此时窗口内已不包含 t 的所有元素,继续从①开始执行,寻找下一个满足条件的窗口长度

要解决的问题

  1. t 数组中各字符的数量怎么存
我采用了STL中的map类,定义 map<char,int> need , 即一个char值对于一个int值,可以把52个大小写字符的 int 先全部置0,然后遍历 t 数组,每次对应的字符加1,即 need[char]++。当窗口移动时,j 向后移动时,如果 t 中有 s[ j ] ,则need[s[ j ]] -- ,i 向后移动时,如果t 中有 s[ j ] ,则need[s[ j ]]++

​ 2.每次都要判断need中的各主键对于的int 是否全为0,太麻烦,怎么办

​ 可以设置一个整型变量 needlens = t 串长度,如果 need[s[ j ]] > 0 时,则needlens 减一,当need[s[ j ]] <= 0 时不减,因为此时s[ j ] 虽是 t 中字符,但窗口中的s[ j ]数量已经满足,这个是多余的;当needlens等于0时,就可以计算当前窗口的大小了

同时在 i 后移的时候,如果need[s[ i ]] < 0,表示这个s[ i ]是多余的,窗口内仍然包含 t 中所有元素,但也要need[s[ i ]]++

一点说明:示例代码中是先找到窗口内不含 t 中所有元素时的第一位,即s[i - 1 ~ j] 还包含t 中所有元素,但s[i ~ j] 中已经不包含 t 中所有元素,所以代码中用的是 i - j + 2

LeetCode原题地址

AC代码
//滑动窗口 
#include<iostream>
#include<algorithm>
#include<string>
#include<map>
using namespace std;
string minstr(string s,string t) {
	int needlens = t.length();
	int minlen = 1e6+10;
	int index;
	map<char,int> need;

	for(int i = 0; i < t.length(); i++) {
		need[t[i]] = need[t[i]] + 1;
	}
	int i = 0, j;
	for(j = 0; j < s.length(); j++) {
		if(t.find(s[j]) < t.length()) {
			if(need[s[j]] > 0)
				needlens--;
			need[s[j]] = need[s[j]] - 1;
			if(needlens == 0) {  //窗口内已包含 t 中所有元素 
				while(needlens == 0) {  //i不断前移,直到窗口内不包含 t 中所有元素 
					if(t.find(s[i]) < t.length() && need[s[i]] == 0) {
						need[s[i]] = need[s[i]] + 1;
						needlens++;
					}
					else if(t.find(s[i]) < t.length() && need[s[i]] < 0)
						need[s[i]] = need[s[i]] + 1;
					i++;
				}
				
				//此时 s[i-1 ~ j]中包含 t 中所有元素,所以最小包含窗口为 j - i + 2 
				if(minlen > j - i + 2)
					index = i - 1;
				minlen = minlen > (j - i + 2) ? (j - i + 2) : minlen;
			}
		}
	}
	if(minlen == 1e6+10)
		return "";
	else
		return s.substr(index,minlen);
}
int main()
{
	string s,t;
	cin>>s>>t;
	cout<<"\""<<minstr(s,t)<<"\""; 
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值