题意分析
- 给出两个字符串S和T,需要我们在S字符串里面找到一个最短的字符串,使得这个这个字符串中包含T字符串里面的所有的字符。题目要求使用O(n)的时间复杂度。题目一定保证如果存在这个字符串,那么这个字符串一定是唯一的。
思路分析
解法一 二分查找
我们看到求最短的字符串,那么很容易可以想到用二分的方法进行求解。我们对这个字符串的长度进行二分,显然,这个答案是有单调性的,如果存在长度为x的字符串满足条件,那么一定存在长度超过x的字符串满足条件。然后就是一个judge函数的写法了,这里我是直接用一个unordered_map存储T字符串的字符的个数,用一个unordered_map存储枚举的字符串的个数,然后遍历T字符串做一个对比。
代码如下
- 二分的时间复杂度为O(logn),对每个二分的结果进行判断的时间复杂度为O(n^2),所以最后的时间复杂度为O(n^2logn)
- 这里需要对字符串中的字符的个数进行哈希,所以空间复杂度为O(n)
class Solution { public: /** * * @param S string字符串 * @param T string字符串 * @return string字符串 */ unordered_map<char,int> mp; // 判断是否存在长度为len的子串满足题目的条件 string judge(string s,string t,int len){ unordered_map<char,int> tmp; for(int i=0;i+len-1<s.size();i++){ tmp.clear(); for(int j=i;j<i+len;j++){ tmp[s[j]]++; } bool flag=true; // 如果这个子串的每个字符的哈希个数小于这个字符串的哈希个数,那么直接退出 for(auto c:t){ if(mp[c]>tmp[c]){ flag=false; break; } } if(flag){ // 如果这个字符串满足题目的条件,那么直接将这个字符串返回 string tmp=""; for(int j=i;j<i+len;j++){ tmp+=s[j]; } // std::cout<<"tmp:"<<tmp<<endl; return tmp; } } return ""; } string minWindow(string S, string T) { // write code here if(S.size()==T.size()){ return S==T?S:""; } for(auto c:T){ mp[c]++; } string res=""; int l=T.size(),r=S.size(),mid; // 对最终的答案的长度进行二分操作 while(l<r){ mid=(l+r)>>1; string tmp=judge(S,T,mid); // std::cout<<tmp<<endl; if(tmp!=""){ r=mid; res=tmp; }else{ l=mid+1; } } //std::cout<<r<<endl; return res; } };
解法二 双指针
- 显然,上面的解法其实是不符合题目的要求的,所以我们就需要继续寻找更优的解法。从上面的二分的情况我们可以看到,其实在枚举长度的时候,我们有很多都是浪费了的。根本没必要进行继续枚举,而是可以跳过一部分。所以,我们找到了双指针的写法。双指针就是利用两个指针的移动维护我们想要维护的值。
在这个题目中,当我们进行双指针维护的时候,右指针移动的条件是当前这个序列还是不符合题目的条件,如果一旦符合题目的要求,右指针停止移动,左指针移动一步。
代码如下
- 双指针只需要对字符串扫一遍就行,时间复杂度为O(n)
- 还是需要使用unordered_map进行哈希,空间复杂度为O(n)
class Solution { public: /** * * @param S string字符串 * @param T string字符串 * @return string字符串 */ // judge判断当前的局面是否满足题目的要求,满足返回true bool judge(string t,unordered_map<char,int> mp1,unordered_map<char,int> mp2){ for(auto c:t){ if(mp1[c]>mp2[c]) return false; } return true; } string minWindow(string S, string T) { // write code here unordered_map<char,int> mp1,mp2; for(auto c:T){ mp1[c]++; } int n=S.size(); string ans=""; int mi=1e9; for(int l=0,r=0;l<n;l++){ // 如果右指针比左指针小,同时右指针每到边界,而且此时还没有到达题目要求 while(l<=r&&r<n&&!judge(T,mp1,mp2)){ mp2[S[r]]++; r++; } // 出来的时候也要判断,因为可能存在右指针到了边界,光左指针进行移动的情况 if(judge(T,mp1,mp2)==false) continue; int len=r-l+1; // 维护最小的子串 if(mi>len){ mi=len; ans=""; for(int i=l;i<r;i++){ ans+=S[i]; } } mp2[S[l]]--; } return ans; } };