30.串联所有单词的子串
30. 串联所有单词的子串 - 力扣(LeetCode)
必看!!!本题是我们上次写的438.异位词的进阶版,可参考本篇文章:算法——滑动窗口(day7)-CSDN博客来帮助理解。
题目解析:
这里我就默认大家都已经看过我们上一篇题解的思路了~
本道题我们可以通过题意:words数组内所有字符串长度相同的特性对问题进行转化~
例如,我们可以把words数组内的其中一个字符串想象成字符a,另一个字符串想象成字符b。然后也对字符串s中进行成队字符串转化(这里是长度为3的字符串转化为1字符)。双方转化完之后我们就可以发现这跟之前的求异位词那道题已经没有什么区别了~
算法解析:
这里我们再来补充一下细节问题:比如right遍历移动的时候应该是跨words数组中的字符串长度进行移动,因为我们把一段子串想象成一个字符,所以哈希表录入的时候都是已子串录入的,那么我们就不能单纯让right一个字符一个字符遍历,而是一段子串一段子串遍历。left也同样如果。不过在遍历完毕后还需要注意,还有绿色线,黄色线这种划分情况,至于黑色线只不过比蓝色线少了一个字符串,所以没必要划分了~
滑动流程图:
其实只要能把问题转化为我们上一题的思路剩下的就简单了,基本思路是一样的,后面再考虑一下两个哈希表的字符串录入,双指针的移动距离,考虑到所有情况的发生就好了。
代码:
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
//记录字符串数组words的数据
unordered_map<string, int>hash2;
for (auto& ch : words)
{
hash2[ch]++;
}
//字符串数组中每个字符串的长度
int m = words[0].size();
//字符串数组中数组的个数
int n = words.size();
//记录结果
vector<int> ret;
int k = 0;
//多层循环做到不遗漏
while (k < m)
{
//在每一层新的循环中都有特定记录字符串s的哈希表
unordered_map<string, int>hash1;
for (int left = k, right = k, count = 0; right + m <= s.size(); right += m)
{
//获取特定长度的字符串录入hash1中
string a = s.substr(right, m);
//正常情况下,窗口长度短时扩充窗口
//先录入所选子串数据
hash1[a]++;
//判定录入的数据是否为可以抵消hash2中对应的有效字符串
if (hash1[a] <= hash2[a])
{
count++;
}
//扩充完发现仍过短就跳到下一循环继续扩充
//当窗口长度过长时候,开始缩小窗口
if (right - left + 1 > m * n)
{
//获取特定长度的字符串
string b = s.substr(left, m);
//删减对应字符串个数
hash1[b]--;
//如果删完后发现缺少了抵消hash2中对应子串可能性
if (hash1[b] < hash2[b])
{
//有效字符串减一
count--;
}
left += m;
}
//当有效字符串个数完全可以抵消时
if (count == n)
{
//录入结果
ret.push_back(left);
}
}
//开始下一轮的循环
k++;
}
return ret;
}
};
76.最小覆盖子串
76. 最小覆盖子串 - 力扣(LeetCode)
题目解析:
这道题依然可以利用两个哈希表进行辅助,不过与其他题不同的是本题只要划分好的s内至少有t对应的字符就行了, 多了也没事。
对暴力进行分析,如果已经找到满足条件的子串那么left在进入下一轮的遍历时right需要复位吗?————不需要,假设【left,right】为目标子串的范围,left继续前进只有2种可能,
- 要么缩小窗口后仍满足条件,这时候只需要让right呆在原地,然后记录满足条件的新子串范围【left,right】即可。
- 要么不符合所需条件,就让right继续前进直到找到所需字符。
所以不管怎么样right都是不用复位的,而这也引出了我们的滑动窗口~
算法解析:
滑动窗口流程图:
之所以找字符种类而不是个数是因为本题只要满足t中所含字符即可,个数超出也无大碍。那我们就以hash2中的个数为基准,假设hash2中a有2个,那么只有hash1中a的个数也达2个跟hash2相等那才能算是一个有效种类。在未达到数量标准之前都不算为有效种类~
基本思路为:
- 进窗口:进之前先录入数据到hash1中,然后判断两表中该字符个数是否相等(若相等,count++)
- 判断,这里是以判断count与m是否相等,若相等则说明找到子串,更新结果,然后不断出窗口直到count不等于m为止(在这个过程可以找出更多子串)
- 出窗口:出之前先判断两表中该字符个数是否相等(若相等,则说明删减后无法匹配,则count--),然后删减数据
代码:
class Solution {
public:
string minWindow(string s, string t) {
//记录字符串s的数据
unordered_map<int, int>hash1;
//记录字符串t的数据
unordered_map<int, int>hash2;
for (auto ch : t)
{
hash2[ch - 'a']++;
}
int count = 0;
int len = INT_MAX;
int begin = -1;
//计算t中字符的种类
int m = hash2.size();
for (int left = 0, right = 0; right < s.size(); right++)
{
//扩充窗口
//录入数据
hash1[s[right] - 'a']++;
//录入完如果两表字符数量相等,则说明count可+1
if (hash1[s[right] - 'a'] == hash2[s[right] - 'a'])
{
count++;
}
//在扩充窗口的过程中如果已经找到可匹配的子串
while (count == m)
{
//记录所找子串长度以及起始位置
if (right - left + 1 < len)
{
len = right - left + 1;
begin = left;
}
//判断在缩小窗口之前两表中字符个数是否相等
if (hash1[s[left] - 'a'] == hash2[s[left] - 'a'])
{
//若相等则说明该字符出窗口后无法与hash2对应字符匹配
count--;
}
//移除该字符
hash1[s[left] - 'a']--;
//缩小窗口
left++;
}
}
//未找到目的子串
if (begin == -1)
{
return "";
}
else
{
return s.substr(begin, len);
}
}
};