3. 无重复字符的最长子串
使用set来存储窗口
int lengthOfLongestSubstring(string s) {
unordered_set<char> set;
int ans = 0, right = 0;
for (int i=0; i < s.length(); i++)
{
// 右指针右移,直到遇到重复字符
while (right < s.length() && 0==set.count(s[right]))
{
set.insert(s[right]);
right++;
}
// 计算下当前的长度
ans = max(ans, right - i);
// 移除最左边的重复字符
if (set.size() > 0)
{
set.erase(s[i]);
}
}
return ans;
30. 串联所有单词的子串
继续滑动窗口,使用了两个hash,tFreq是记录目标窗口中各个单词出现的频数,winFreq记录当前窗口中各个单词出现的频数,同时使用distance记录两个窗口的差异。
由于是单词比较,s 的起点不同,所划分的单词也不同,所以大循环是用单词长度划分 s
在窗口的滑动中,要处理下面3中情况
- 新的单词并不在words里
- 遇到了重复次数过多的单词
- 完全匹配
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ans = {};
unordered_map<string, int> winFreq, tFreq; // 当前窗口的单词出现频数 和 目标的单词出现频数
int left=0, right = 0, distance = words.size(); // distance 表示 当前窗口与目标窗口的差异, 0 表示完全匹配
int lenWords = 0;
int lenS = s.length();
if (words.size() == 0 || s.length() == 0)
{
return ans;
}
int wordSize = words[0].size();
lenWords = wordSize * words.size();
if (lenS < lenWords)
{
return ans;
}
for (string s : words)
{
tFreq[s]++;
}
// 将 s 以单个单词长度分类
for (int start = 0; start < wordSize; start++)
{
// 每一轮的初始条件(曾因为 distance 和 winFreq 没有重置导致多次提交失败)
left = start, right = start, distance = words.size(), winFreq.clear();
while(right + wordSize <= lenS)
{
string subStr = s.substr(right, wordSize);
// 1. 存在, 是目标单词
if (tFreq.find(subStr) != tFreq.end())
{
winFreq[subStr]++;
distance--;
// 有重复,需要去重, 缩小窗口
while (winFreq[subStr] > tFreq[subStr])
{
string firstStr = s.substr(left, wordSize);
winFreq[firstStr]--;
distance++;
left += wordSize;
}
}
// 2. 不存在
else
{
left = right + wordSize;
distance = words.size();
winFreq.clear();
}
if (0 == distance)
{
ans.push_back(left);
}
right += wordSize;
}
}
return ans;
}
};
76. 最小覆盖子串
继续滑动,保持左边窗口不变,右窗口滑动,直到匹配所有字符,然后考虑缩减左边窗口;
数据结构上,使用hash记录当前窗口是否已经包含了t中的所有字符;同时利用负数表示不需要的字符,正数表示需要的字符,避免了使用find
class Solution {
public:
string minWindow(string s, string t) {
// set hash queue stack list tree
const int lenS = s.length(), lenT = t.length();
int ansLen = INT_MAX, start = 0;
int left = 0, right = 0, k = lenT;// k 记录当前所需字符数
vector<int> need(128, 0); // 记录当前窗口下t中各个字符缺少的个数
// 初始化,缺t中的所有字符
for (int i=0; i<lenT; i++)
{
need[t[i]]++;
}
for (right = 0; right < lenS; right++)
{
if (need[s[right]] > 0)
{
k--;
}
need[s[right]]--; // 需要数减 1
if (k == 0) // found all
{
// 尝试缩减窗口
while (left < right && need[s[left]] < 0) // 窗口左边存在不需要的字符
{
need[s[left]]++;
left++;
}
// 遇到了第一个需要的字符,此时窗口符合要求
if (right - left + 1 < ansLen)
{
ansLen = right-left+1;
start = left;
}
// 窗口左移一位,释放need[s[left]]
need[s[left]]++;
left++;
k++;
}
}
if (ansLen == INT_MAX)
{
return "";
}
else
{
return s.substr(start, ansLen);
}
}
};
159. 至多包含两个不同字符的最长子串
使用hash记录当前窗口下各个字符的个数,使用计数器count记录窗口中不相同的字符个数;当count > 2时,就需要记录当前「可行」窗口大小,并缩小左边窗口,删除1个旧字符。
考虑的特殊情况有:
- s 中只有2个不相同的字符
- s 的长度小于3
int lengthOfLongestSubstringTwoDistinct(string s) {
int lenS = s.length();
int left =0, right = 0;
int ans = 0;
int count = 0, k = 2;
unordered_map<char, int> hash;
if (lenS <= k)
return lenS;
for (right =0; right < lenS; right++)
{
hash[s[right]]++;
if (hash[s[right]] == 1) // 遇到新字符
{
count++;
}
// 遇到了第三个字符,缩减左边窗口,删除1个字符
if (count > k)
{
// 计算当前子串长度
ans = max(ans, right-left);
while(count > k && left < right)
{
hash[s[left]]--;
if (hash[s[left]] == 0)
{
count--;
}
left++;
}
}
}
ans = max(ans, right-left); // 考虑只有2个非重复字符的情况
return ans;
}