leetcode 30. 串联所有单词的子串----滑动窗口篇八
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode 30. 串联所有单词的子串----滑动窗口篇八相关的知识,希望对你有一定的参考价值。
串联所有单词的子串题解集合
暴力匹配版滑动窗口
思路:
首先,最直接的思路,判断每个子串是否符合,符合就把下标保存起来,最后返回即可。
- 首先这里滑动窗口的大小是固定的,为words数组中的元素个数乘以单词长度,这里words数组中每一个单词的长度均相等
- 那么只需要用两个指针l和r,固定区间为[l,r)的滑动窗口,然后检查当前[l,r)的滑动窗口是不是满足与words数组中的单词完全匹配,如果满足,保存下标,然后更新区间,即区间整体右移一位,继续找其他解。
- 一旦在滑动窗口内发现不匹配的子串,就更新区间,另寻解
- 这里如何判断当前滑动窗口内的所有子串都与words数组完全匹配呢?
- 我们需要copy一份words数组,如果当前子串s与copy数组中某个字符串匹配,那么就将对应的字符串从copy数组中删除,然后继续去看区间剩余子串匹配情况。
- 只有当copy数组中最后元素都被删除时,才表明当前滑动区间满足题意。
- 并且每当我们需要去更新滑动区间时,同样也需要重新copy一下words数组
代码:
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words)
{
//记录下标
vector<int> index;
//这里单词长度相同,下面记录单词长度
int wordSize = words[0].size();
//滑动窗口长度
int len = 0;
//计算滑动窗口的固定长度
for (auto ss : words)
len += ss.size();
//双指针---滑动区间:[l,r)
int l = 0, r = len;
while (r <= s.size())
{
//当滑动窗口更新时,同步更新需要查找的单词数组
vector<string> Words = words;
//记录剩余需要匹配的个数
int leftNum = words.size();
//测试当前滑动区间内的所有单词是否与所给字符串匹配
for (int i = l; i<r; i+=wordSize)
{
string w = s.substr(i, wordSize);
auto ret = find(Words.begin(), Words.end(), w);
//获取s中从i位置开始的wordsize个字符大小的子串,查找是否在words中
if ( ret== Words.end())
//如果有一个子串不存在,说明当前区间不符合要求
break;
else
{
//每匹配完一个子串,就去对应的查找数组中删除已经找到的字符串
Words.erase(ret);
leftNum--;//当前子串匹配,说明少了一个需要匹配的子串
}
//当前滑动区间所有子串都匹配完毕
if (leftNum == 0)
{
index.push_back(l);
break;
}
}
//如果当前滑动窗口满足条件,并且r还没有越界,那么窗口左移,寻找其他解
l++,r++;
if (r > s.size()) break;
}
return index;
}
};
用哈希优化暴力滑动
思路:
使用两个哈希表,一个记录words数组中每个字符串出现的次数,一个记录当前滑动窗口中每一个字符串出现的次数。
如果滑动窗口当前查找的子串,不存在于words数组中,那么直接更新滑动区间。
如果滑动窗口当前查找的子串,存在于words数组中,但是出现次数超过了words数组中对应字符串出现的次数,那么也不符合,直接更新区间。
哈希容器作用体现:
判断当前子串的出现次数是否超过了对应words数组中该串出现的次数。
图解:
我们把 words 存到一个 HashMap 中。
然后遍历当前滑动区间的每个单词。
第一个单词在 HashMap1 中,然后我们把 foo 存到 HashMap2 中。并且比较此时 foo 的 value 和 HashMap1 中 foo 的 value,1 < 2,所以我们继续扫描。
第二个单词也在 HashMap1 中,然后把 foo 存到 HashMap2 中,因为之前已经存过了,所以更新它的 value 为 2 ,然后继续比较此时 foo 的 value 和 HashMap1 中 foo 的 value,2 <= 2,所以继续扫描下一个单词。
第三个单词也在 HashMap1 中,然后把 foo 存到 HashMap2 中,因为之前已经存过了,所以更新它的 value 为 3,然后继续比较此时 foo 的 value 和 HashMap1 中 foo 的 value,3 > 2,所以表明该字符串不符合。然后更新滑动区间
当然上边的情况都是单词在 HashMap1 中,如果不在的话就更好说了,不在就表明当前区间肯定不符合了,直接更新当前滑动区间。
代码:
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words)
{
//记录下标
vector<int> index;
//这里单词长度相同,下面记录单词长度
int wordSize = words[0].size();
int len = 0; //滑动窗口长度
//记录words数组中每个字符串出现的次数
unordered_map<string, size_t> W;
for (auto w : words)
{
//先判断当前元素是否已经出现过了
if (W.find(w)==W.end())
//第一次出现
W[w] = 1;
else
W[w]++;
}
//计算滑动窗口的固定长度
for (auto ss : words)
len += ss.size();
//双指针---滑动区间:[l,r)
int l = 0, r = len;
while (r <= s.size())
{
//记录当前区间中每个字符串出现的次数
unordered_map<string, int> ww;
int leftNum = words.size();
//测试当前滑动区间内的所有单词是否与所给字符串匹配
for (int i = l; i<r; i+=wordSize)
{
string w = s.substr(i, wordSize);
auto ret = find(words.begin(), words.end(), w);
//获取s中从i位置开始的wordsize个字符大小的子串,查找是否在words中
if ( ret== words.end())
//如果有一个子串不存在,说明当前区间不符合要求
break;
else
{
//先判断当前字符串在当前区间中是第一次出现吗?
if (ww.find(w)==ww.end())
ww[w] = 1;
else
{
//判断当前字符串出现次数是否大于words数组中对应字符串的出现次数
if (++ww[w] > W[w])
break;
}
leftNum--;
}
//当前滑动区间所有子串都匹配完毕
if (leftNum == 0)
{
index.push_back(l);
break;
}
}
//如果当前滑动窗口满足条件,并且r还没有越界,那么窗口左移,寻找其他解
l++,r++;
if (r > s.size()) break;
}
return index;
}
};
滑动距离优化+哈希优化
思路:
在上面的解法中,我们每一次移动一个字符
现在为了方便讨论,我们每次移动一个单词的长度,也就是 3 个字符,这样所有的移动被分成了三类。
第一类: 当子串完全匹配,移动到下一个子串的时候。
因为当前区间满足条件,那么下一次滑动区间就可以直接往右移动一个单词的长度。
并且看图,由于前面两个foo其实已经判断过了,是匹配的,因此我们可以直接从第三个foo的位置,即判断新加入的单词是否满足条件即可。
那么这里我们就不需要每一次都更新ww哈希容器,只需要移除离开滑动区间的单词bar即可。
第二类: 当判断过程中,出现不符合的单词。
如果区间只是一格一格的移动,那么不符合的单词the永远停留在区间内部,那么该区间就永远不会满足条件,直到当前区间不包含the为止,因此我们可以直接将滑动区间移动到不符合区间后面的位置。
第三类:判断过程中,出现的是符合的单词,但是次数超了。
当前区间的bar单词出现了两次,而所给的words数组中只出现了一次,显然不满足条件,因此我们需要调整滑动区间位置直到当前区间每个元素出现的次数满足条件为止。
代码:
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
if(words.empty()) return {};
unordered_map<string,int> wordmap,smap;
for(string word:words) wordmap[word]++;
int wordlen = words[0].size();
int wordnum = words.size();
vector<int> ans;
for(int k=0;k<wordlen;k++){
int i=k,j=k;
while(i<s.size()-wordnum*wordlen+1){
while(j<i+wordnum*wordlen){
string temp = s.substr(j,wordlen);
smap[temp]++;
j+=wordlen;
if(wordmap[temp]==0){//情况二,有words中不存在的单词
i=j;//对i加速
smap.clear();
break;
}
else if(smap[temp]>wordmap[temp]){//情况三,子串中temp数量超了
while(smap[temp]>wordmap[temp]){
smap[s.substr(i,wordlen)]--;
i+=wordlen;//对i加速
}
break;
}
}
//正确匹配,由于情况二和三都对i加速了,不可能满足此条件
if(j==i+wordlen*wordnum){
ans.push_back(i);
smap[s.substr(i,wordlen)]--;
i+=wordlen;//i正常前进
}
}
smap.clear();
}
return ans;
}
};
以上是关于leetcode 30. 串联所有单词的子串----滑动窗口篇八的主要内容,如果未能解决你的问题,请参考以下文章
算法leetcode|30. 串联所有单词的子串(rust重拳出击)