方法一
实现思路
实现思路是参照了【LeetCode3】-无重复字符的最长子串
双指针+字符哈希
1.首先统计所有匹配的子串出现的字符的种类及has_cnt数量,然后遍历许匹配的字符串
2.同时在这个过程中记录当前的子串word,以及维护字符串s在这个过程中每个字符动态变化的数量cnt
当cnt的数量大于has_cnt的时候,尝试移动begin
————begin能移动的条件为cnt[begin]>has_cnt
3.每次新产生的word子串都要判断一下是否满足条件且小于当前最短的子串结果
判断子串满足条件的逻辑:
word中统计所有在字符串t出现的字符的数量,!!!这里面注意在匹配的时候要考虑数量,如果该子串中所有在字符串t出现的数量均大于等于字符串t的要求,则该子串满足条件
实现代码(超时)
class Solution {
public:
bool has_all(string word,set<char> &has,vector<int> &has_cnt,string ss){
vector<int> t(256,0);
for(int i=0;i<word.size();i++){
if(has.count(word[i]))
t[word[i]]++;
}
int sum=0;
for(int i=0;i<256;i++){
if(t[i]>0&&t[i]>=has_cnt[i])
sum++;
}
return sum==has.size();
}
string minWindow(string s, string t) {
string re="";
int min_len=s.length();
set<char> has;
vector<int> has_cnt(256,0);
vector<int> cnt(256,0);
for(int i=0;i<t.length();i++){
has.insert(t[i]);
has_cnt[t[i]]++;
}
// cout<<has_cnt['A']<<endl;
// cout<<has_cnt['C']<<endl;
int begin=0;
string word;
for(int i=0;i<s.length();i++){
cnt[s[i]]++;
if(cnt[s[i]]>has_cnt[s[i]]&&cnt[s[begin]]>has_cnt[s[begin]]){
cnt[s[begin]]--;
begin++;
while(begin<i&& cnt[s[begin]]>has_cnt[s[begin]]){//!has.count(s[begin])){
cnt[s[begin]]--;
begin++;
}
word+=s[i];
if(has_all(word,has,has_cnt,t)&&word.length()<=min_len){
re=word;
min_len=re.length();
}
word="";
for(int j=begin;j<=i;j++)
word+=s[j];
if(has_all(word,has,has_cnt,t)&&word.length()<=min_len){
re=word;
min_len=re.length();
}
}
else{
word+=s[i];
if(has_all(word,has,has_cnt,t)&&word.length()<=min_len){
re=word;
min_len=re.length();
}
}
// cout<<word<<endl;
}
return re;
}
};
提交结果及分析
超时的原因分析,其实本身字符串的遍历是一个线性的过程,问题应该出在验证子串是否满足条件的限制上
实现代码(改进版本)
改进的思路:主要是针对于检验子串方面,其实本身的程序逻辑保证当前子串只要有字符满足要匹配的条件之后,就不可能会移动导致该字符不满足条件,所以判断的时候用sum来累加满足条件的字符数量,这个数量只增不减,判断这个sum是等于字符串t要满足的字符种类时,其实就表明来该子串符合题目的要求,就不必像之前的代码逻辑每次进行计算来
class Solution {
public:
string minWindow(string s, string t) {
string re="";
int min_len=s.length();
set<char> has;
vector<int> has_cnt(256,0);
vector<int> cnt(256,0);
for(int i=0;i<t.length();i++){
has.insert(t[i]);
has_cnt[t[i]]++;
}
int begin=0;
string word;
int sum=0;
for(int i=0;i<s.length();i++){
cnt[s[i]]++;
if(cnt[s[i]]==has_cnt[s[i]]&&has.count(s[i])) sum++;
if(cnt[s[i]]>has_cnt[s[i]]&&cnt[s[begin]]>has_cnt[s[begin]]){
cnt[s[begin]]--;
begin++;
while(begin<i&& cnt[s[begin]]>has_cnt[s[begin]]){//!has.count(s[begin])){
cnt[s[begin]]--;
begin++;
}
word+=s[i];
if(sum==has.size()&&word.length()<=min_len){
re=word;
min_len=re.length();
}
word="";
for(int j=begin;j<=i;j++)
word+=s[j];
if(sum==has.size()&&word.length()<=min_len){
re=word;
min_len=re.length();
}
}
else{
word+=s[i];
if(sum==has.size()&&word.length()<=min_len){
re=word;
min_len=re.length();
}
}
// cout<<word<<endl;
}
return re;
}
};
提交结果及分析
时间复杂度O(n)
方法二
(实际上和方法一的思路是相同的)
实现思路
遍历s和t计算两个字符串中,各个字符出现的次数
再遍历t统计下t中出现过的字符
在判断是否包含时,只需要看t中所有出现的字符的个数对应在s中的个数相比较,都是s的个数要大于等于t的个数
实现代码
总结
这里面都是利用矛盾点进行算法设置,矛盾点在于包含所有字符,且要求字符串长度最短
方法一和方法二的实现思路是相同的
方法一用一个变量来记录满足条件的字符,另使用一个变量来保存子串
方法二每次需要利用函数来判断是否满足来所有字符的要求,子串通过原字符串截断获取