滑动窗口模板
int left=0,right=0;
while(right<s.size())
{
//增大窗口
window.add(s[right++]);
//缩小窗口
while(window need shrink)
window.remove(s[left++]);
}
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
3-无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
解题思路
如果我们依次递增地枚举子串的起始位置,那么子串的结束位置也是递增的!这里的原因在于,假设我们选择字符串中的第 k 个字符作为起始位置,并且得到了不包含重复字符的最长子串的结束位置为rk。那么当我们选择第 k+1 个字符作为起始位置时,首先从 k+1 到 rk的字符显然是不重复的,并且由于少了原本的第 k个字符,我们可以尝试继续增大 rk,直到右侧出现了重复字符为止。
这样一来,我们就可以使用**「滑动窗口」**来解决这个问题了:
我们使用两个指针表示字符串中的某个子串(或窗口)的左右边界,其中左指针代表着上文中「枚举子串的起始位置」,而右指针即为上文中的 rk;在每一步的操作中,我们会将左指针向右移动一格,表示 我们开始枚举下一个字符作为起始位置,然后我们可以不断地向右移动右指针,但需要保证这两个指针对应的子串中没有重复的字符。在移动结束后,这个子串就对应着 以左指针开始的,不包含重复字符的最长子串。我们记录下这个子串的长度;
在枚举结束后,我们找到的最长的子串的长度即为答案。
判断重复字符
在上面的流程中,我们还需要使用一种数据结构来判断 是否有重复的字符,常用的数据结构为哈希集合(即 C++ 中的 std::unordered_set,Java 中的 HashSet,Python 中的 set, JavaScript 中的 Set)。在左指针向右移动的时候,我们从哈希集合中移除一个字符,在右指针向右移动的时候,我们往哈希集合中添加一个字符。
至此,我们就完美解决了本题。
C++实现
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n=s.size();
if(n==0)
return 0;
int left=0,right=0,res=1;
unordered_set<char>set;
while(right<n-1)
{
set.insert(s[right++]);
//此处可优化
while(set.count(s[right]))
{
set.erase(s[left++]);
}
res=max(res,right-left+1);
}
return res;
}
};
- 时间复杂度:O(N),其中 N 是字符串的长度。左指针和右指针分别会遍历整个字符串一次。
- 空间复杂度:O(∣Σ∣),其中 Σ 表示字符集(即字符串中可以出现的字符),∣Σ∣ 表示字符集的大小。在本题中没有明确说明字符集,因此可以默认为所有 ASCII 码在 [0,128) 内的字符,即 ∣Σ∣=128。我们需要用到哈希集合来存储出现过的字符,而字符最多有∣Σ∣ 个,因此空间复杂度为 O(∣Σ∣)。
438-找到字符串中所有字母异位词
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
解题思路
根据题目要求,我们需要在字符串 s 寻找字符串 p 的异位词。因为字符串 p 的异位词的长度一定与字符串 p 的长度相同,所以我们可以在字符串 s中构造一个长度为与字符串 p 的长度相同的滑动窗口,并在滑动中维护窗口中每种字母的数量;当窗口中每种字母的数量与字符串 p 中每种字母的数量相同时,则说明当前窗口为字符串 p 的异位词。
算法
在算法的实现中,我们可以使用数组来存储字符串 p 和滑动窗口中每种字母的数量。
细节
当字符串 s 的长度小于字符串 p 的长度时,字符串 s 中一定不存在字符串 p 的异位词。但是因为字符串 s 中无法构造长度与字符串 p 的长度相同的窗口,所以这种情况需要单独处理。
C++实现
leetcode官方题解
充分利用STL中可以直接判断vector是否相等的便利性
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
int sLen = s.size(), pLen = p.size();
if (sLen < pLen) {
return vector<int>();
}
vector<int> ans;
vector<int> sCount(26);
vector<int> pCount(26);
for (int i = 0; i < pLen; ++i) {
++sCount[s[i] - 'a'];
++pCount[p[i] - 'a'];
}
if (sCount == pCount) {
ans.emplace_back(0);
}
for (int i = 0; i < sLen - pLen; ++i) {
--sCount[s[i] - 'a'];
++sCount[s[i + pLen] - 'a'];
if (sCount == pCount) {
ans.emplace_back(i + 1);
}
}
return ans;
}
};
套用模板实现
vector<int> findAnagrams(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
vector<int> res; // 记录结果
while (right < s.size()) {
char c = s[right];
right++;
// 进行窗口内数据的一系列更新
if (need.count(c)) {
window[c]++;
if (window[c] == need[c])
valid++;
}
// 判断左侧窗口是否要收缩 while可以替换为if,这里仅为了保持模板一致性
while (right - left >= t.size()) {
// 当窗口符合条件时,把起始索引加入 res
if (valid == need.size())
res.push_back(left);
char d = s[left];
left++;
// 进行窗口内数据的一系列更新
if (need.count(d)) {
if (window[d] == need[d])
valid--;
window[d]--;
}
}
}
return res;
}
- 时间复杂度:O(m+(n−m)×Σ),其中 n 为字符串 s 的长度,m 为字符串 p 的长度,Σ 为所有可能的字符数。我们需要 O(m) 来统计字符串 p 中每种字母的数量;需要O(m) 来初始化滑动窗口;需要判断 n−m+1 个滑动窗口中每种字母的数量是否与字符串 p 中每种字母的数量相同,每次判断需要 O(Σ) 。因为 s 和 p 仅包含小写字母,所以Σ=26。
- 空间复杂度:O(Σ)
76-最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
解题思路
套用滑动窗口模板
C++实现
class Solution {
public:
string minWindow(string s, string t) {
if(s.size()<t.size())
return "";
int left=0,right=0,valid=0,Ind=0,leng=INT_MAX;
unordered_map<char,int>need,window;
for(char c:t)
need[c]++;
while(right<(int)s.size())
{
char c=s[right++];
if(need.count(c))
{
window[c]++;
if(window[c]==need[c])
valid++;
}
while(valid==need.size())
{
char d=s[left];
if(need.count(d))
{
if(window[d]==need[d])
valid--;
window[d]--;
}
if(leng>right-left)
{
Ind=left;
leng=right-left;
}
left++;
}
}
return leng==INT_MAX?"":SubMinWindow(s,Ind,leng);
}
//string复制函数
string SubMinWindow(string s,int begin,int length)
{
// int n=sizeof(s);
int n=s.size();
//string t="";
string t(s,begin,length);
return t;
}
};
- 时间复杂度:最坏情况下左右指针对 s 的每个元素各遍历一遍,哈希表中对 s 中的每个元素各插入、删除一次,对 t 中的元素各插入一次。每次检查是否可行会遍历整个 t的哈希表,哈希表的大小与字符集的大小有关,设字符集大小为 C,则渐进时间复杂度为 O(C⋅∣s∣+∣t∣)。
- 空间复杂度:这里用了两张哈希表作为辅助空间,每张哈希表最多不会存放超过字符集大小的键值对,我们设字符集大小为 C ,则渐进空间复杂度为O©。