🎬 胖咕噜的稞达鸭 :个人主页
🔥 个人专栏 : 《数据结构》《C++初阶高阶》
《Linux系统学习》
《算法入门》
⛺️技术的杠杆,撬动整个世界!

找到字符串中所有的字母异位词
找到异位词之后要返回在s字符串中的起始下标
算法原理:
如何快速判断两个字符串是否是"异位词"?
利用哈希表:
将要比较的p字符放到hash1中记录p的len长度m,以及每一个字符出现的次数;
在hash2中遍历,每遍历m个字符放到hash2中,跟hash1对比,如果每个字符的出现次数跟hash1中相等,就说明此时hash2中的字符串满足异位词特点,
返回首字符的下标。
随后要清空hash2,从left+1的位置进hash2。(这一步需要优化:不需要清空hash2,只需要将hash2的第一个字符丢出去,将hash2的下一个字符放进来,这样就少了重复字符的进入哈希表,可以节省效率)
也就是说这个滑动窗口的大小是固定的,固定大小的滑动窗口一个字符一个字符移动
解法:
1.left=0,right=0;
2.进窗口;(hash2[in]++)
- 判断 (right - left +1 > m)
出窗口 (hash2[out]--)
更新结果(check(hash2,hash1))
更新结果的判断条件:利用变量count来统计窗口中"有效字符"的个数
- 进窗口的时候维护:进入后
hash2[in]<=hash1[in],就说明是有效字符->count++ - 出窗口:
hash2[out]<=hash1[out]->有效字符->count-- - 更新结果:
count=m
cpp
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> ret; // 用来存储返回结果,字符串起始地址
int hash1[26] = {0};
for (auto ch : p)
hash1[ch - 'a']++; // 记录hash1中的字符
int hash2[26] = {0};
int m = p.size();
for (int left = 0, right = 0, count = 0; right < s.size(); right++) {
char in = s[right];//取出当前右边界的字符in(即将加入窗口的字符)
if (++hash2[in - 'a'] <= hash1[in - 'a'])
count++;//如果更新后的计数 ≤ hash1中该字符的计数(说明这个字符是p中包含的,且窗口内数量未超标),则count++(有效字符数 + 1)
if (right - left + 1 > m) {
char out = s[left++];//取出当前左边界的字符out(即将丢出窗口的字符,并将窗口缩小)
if (hash2[out - 'a']-- <= hash1[out - 'a'])
count--;//如果移出前的计数 ≤ hash1中该字符的计数(说明这个字符原本是 "有效字符"),则count--(有效字符数 - 1);
}
//更新结果
if (count == m)
ret.push_back(left);
}
return ret;
}
};
串联所有的子串:

算法原理
s="barfoothefoobarman"
w="foo","bar"
对于这个题目,寻找字串;可以先将w中的"foo"近似于a,"bar"近似于b,
cpp
s=" bar foo the foo bar man"
s= b a c a b d
解法:滑动窗口+哈希表
- 哈希表:
- Left和right指针的移动:移动的步长是单词的长度->
len - 滑动窗口执行的次数:从索引为0的位置len=3划分每一段的长度;从索引为1的位置len=3划分每一段的长度;从索引为2的位置len=3划分每一段的长度。从索引为3的位置len=3划分每一段的长度(有重复,所以就用三种方法划分每一段的长度)。
cpp
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int>ret;//用来存储返回的下标
unordered_map<string,int>hash1;//保存words里面所有单词的频次
for(auto& ch:words)hash1[ch]++;
int len=words[0].size(),m=words.size();
for(int i=0;i<len;i++)
{
unordered_map<string,int>hash2;//维护窗口内单词的频次
for(int left=i,right=i,count=0;right+len<=s.size();right+=len)
{
//进窗口+维护count
string in =s.substr(right,len);//从right位置取长度为len的子串,即要加入窗口的单词in
hash2[in]++;//将新加入的单词in计入hash2(窗口内该单词的词频+1)
if(hash2[in]<=hash1[in])count++;//这个单词是words中包含的,且窗口内数量未超标(是 "有效" 的),因此count++
//判断
if(right-left+1>m*len)
{
//出窗口+维护count
string out = s.substr(left,len);
if(hash2[out]<=hash1[out])count--;//说明这个单词原本是 "有效" 的,移出后有效数减少,count--;
hash2[out]--;
left +=len;
}
//更新结果
if(count == m)ret.push_back(left);
}
}
return ret;
}
};
最小覆盖子串

举例子:
s: A D O B E C O D E B A N C
t: A B C
将t放入hash2中;
解法:
- left = 0 ,right = 0;
- 进窗口:(hash2[in]++)
- 判断:(check(hash1,hash2))
- 更新结果->起始位置,最短长度
- 出窗口:hash2[out]--,一直出窗口到不合法为止
优化:如果用两个哈希表,优化判断条件,这里通过一个变量count来标记有效字符的种类
t:ABC A:1 B:1 C:1
s:ABBCA
- 进窗口(进入之后,当
hash2[in] == hash1[in],count++,有效字符++,继续判断); - 出窗口(出窗口之前,当
hash2[out] == hash1[out],count--,有效字符--,继续判断合适的字符串) - 判断条件(
count == hash1.size(),统计哈希表的长度)
cpp
class Solution {
public:
string minWindow(string s, string t) {
int hash1[128]={0};//统计字符串t中的每一个字符的频次
int kinds=0;//统计有效字符的种类
for(auto ch:t)
{
if(hash1[ch] == 0)kinds++;
hash1[ch]++;//if(hash1[ch]++ == 0)kinds++;
}
int hash2[128]={0};//统计窗口内每一个字符出现的频次
int minlen =INT_MAX,begin=-1;
for(int left=0,right=0,count=0;right<s.size();right++)
{
char in = s[right];
if(++hash2[in]==hash1[in])count++;
while(count == kinds)//判断条件
{
if(right-left+1< minlen)//更新结果
{
minlen = right -left +1;
begin =left;
}
char out =s[left++];
if(hash2[out]-- == hash1[out])count--;
}
}
if(begin == -1)return "";
else return s.substr(begin,minlen);
}
};

