一.找到字符串里面所有的异位词
1.题目
初次看到此题我们一般默认对p 这个字符串里面的字符进行排列组合:
"abc" "acb" ''bac'' '' bca'' '' cba'' '' cab'' 之后对 s 这个字符串进行暴力遍历,若存在则返回
**"起始"**位置的下标
其实,不妨换个思路:p这个字符串里面每一个字符出现的次数是固定的,我们只需要记录每一个
字符出现的次数,对s字符串进行遍历只要是对应字符出现的次数相同,那么就是所谓的"字母异位
词"。
借助2个哈希表:记录每一个字符出现的次数
解法一:暴力求解 + 哈希表
解法二:滑动窗口 + 哈希表
2.算法原理
1)暴力求解:定义2个"哈希表",hash1 [26] 记录p 里面每一个字符出现的次数
定义2个指针 left = 0,right = 0;
**hash2[26]**记录s 这个字符串里面每一个字符出现的次数
同时记录一下p字符串的大小:len,在s 里面每隔len 个字符进行判断一个是否相等
2)滑动窗口:我们发现每一次进行新的比较的时候,right 指针都需要再次回到left 指向的下一个
位置,其实这是没有必要 的,在[let, right]这个区间最大的字符有效个数是固定的,所有每次right
指针可以停留在"原来的位置",对left 指针进行更新:left 指针++
这不"滑动窗口"的思想就来了。
之所以把哈希表的大小设置为26,是因为题目已给出说明:2个字符串的均是由小写字母组成的。
3.分析
4·OJ代码
版本1的代码:
cpp
vector<int> findAnagrams(string& s, string& p)
{
vector<int>ret;
int hash1[26] = {0};//记录p 里面字符出现的次数
int hash2[26] = {0};//记录s 里面字符出现的次数
for(auto it :p)
hash1[it-'a']++;
int left = 0,right = 0,len = p.size();
int n = s.size();
for(;right < n;right++)
{
//进窗口
char in = s[right];
hash2[in-'a']++;
//判断 & 出窗口
if(right-left+1 > len )
{
//删除left对应元素出现的次数
char out = s[left++];
hash2[out-'a']--;
}
//结果更新
if(right-left+1 == len)
{
int i = 0,j = 0;
int flag = 1;//标志位:默认2个哈希表一样
//先判断2个哈希表是否一致
while(i < 26)
{
if(hash1[i++] != hash2[j++])
{
flag = 0;
break;
}
}
if(flag)
ret.push_back(left);
else
flag = 1;//及时更新,便于下一次判断
}
}
// 可能存在righ 走到n的时候,此时元素个数 [left right-1] == len
if(len == right-left+1 )
{
int i = 0,j = 0;
int flag = 1;//标志位:默认2个哈希表一样
while(i < 26)
{
if(hash1[i++] != hash2[j++])
{
flag = 0;
break;
}
}
if(flag)
ret.push_back(left);
}
return ret;
}
咱来看看运行结果咋样~~~
其实对于这个更新结果里面的判断部分咱们可以进行优化的,虽然说优化前后时间复杂度都是
O(N) 级别,但是思想是很巧妙的
**采用计数方法对count :**表示滑动窗口内有效的字符个数,注意不同的字符才算有效的字符。
分析见下:
优化后的代码:
cpp
vector<int> findAnagrams(string& s, string& p)
{
int hash1[26] = { 0 };//统计p 里面每个字符出现的有效字符个数
int hash2[26] = { 0 };
for (auto it : p)
hash1[it - 'a']++;
int len = p.size();
vector<int>ret;
//滑动窗口
for (int left = 0, right = 0, count = 0; right < s.size(); right++)
{
//进窗口
// hash2[s[right]-'a']++;
//进窗口的同时要维护count(滑动窗口的有效字符个数)
// if (++hash2[s[right] - 'a'] <= hash1[s[right] - 'a'])//同一个字符可能出现不止1次
char in = s[right];
if (++hash2[in- 'a'] <= hash1[in - 'a'])//同一个字符可能出现不止1次
count++;
//出窗口 这时候只需要出一个元素即可
//注意 不管hash2[s[left]-'a'] > hash1[p[left]-'a'] 还是<= 都需要--hash2[s[right]-'a']
if (right - left + 1 > len)
{
char out = s[left++];
if (hash2[out- 'a']-- <= hash1[out - 'a'])//条件不管是否满足left 对应 的元素都需要删除
count--;//此时删除的是一个有效字符
}
// else
// hash1[s[left]-'a']--;//删除left 指向的元素
//结果更新
if (count == len)
{
ret.push_back(left);
}
}
return ret;
}
此时的运行效果:
二·串联所有单词的子串
1.题目
其实本题可以简化为找单词的异位词 ,只不过这里的单词的长度是固定的:words[0] .size
这里借助上面找字母异位词的思想,我们发现其实这道题并不是那么困难哈~~~
注意这里有一个关键的条件:就是words 数组里面的所有单词的大小都是固定的,
记录为len = words[0].size(); 注意因为里面每一个元素都是字符串,需要借助size()函数进行求出
每一个单词的大小。
把words这个数组的大小记录为 sz = words.size():表示words 数组里面字符串的个数多少
2.算法原理
滑动窗口 + count 计数
在这直接上"滑动窗口",对于暴力求解的算法就不在一一叙述了
还是老问题,每当对[left rigth ]指定的区间进行遍历完后,right 指针没有必要再次回到left 指向的
下一个位置,对于这个区间有效单词的 个数是一定的,只需要一定left 指针所在的位置即可。
依然还是下面的几个步骤:
进窗口+维护count
出窗口+维护count
结果更新+维护count
3.分析
4·OJ代码
cpp
vector<int> findSubstring(string& s, vector<string>& words)
{
unordered_map<string,int> hash1;//记录words里面每一个单词出现的次数
for(auto it:words)
hash1[it]++;
int sz = words.size();//单词的总个数
int len = words[0].size();//每一个单词的长度
int n = s.size();
vector<int>ret;
for(int i = 0;i< len ;++i)
{
int count = 0;//记录滑动窗口内在words里面的单词个数
unordered_map<string,int>hash2;//记录s里面每一个单词出现的次数
for(int left = i,right = i; right+len <= n;right += len)//注意条件判断:必须保证进入之后走len步长是一个有效单词
{
//进窗口
string in = s.substr(right,len);//借助函数取到指定的单词
if(++hash2[in] <= hash1[in])
count++;//进入的是一个有效的单词
//出窗口
if(right-left+1 > len*sz)
{
string out = s.substr(left,len);
left +=len;//注意这里是移动len个步长
if(hash2[out]-- <= hash1[out])
count--;//删除的是一个有效单词
}
if(count == sz)
ret.push_back(left);
}
}
return ret;
}
运行结果见下:
三·最小覆盖子串
1.题目
2.算法原理
关于滑动窗口的OJ 题目,也是做了不少的,无非就是双指针的移动 + "4步走"
进窗口;判断;出窗口;结果更新。
这个题与之前略微还是有点不同;采用额外的两个变量 kind ,count进行记录
kind 记录:t 字符串有效的字符个数(非首次出现的字符不会进行记录)
count:记录窗口内有效的字符个数
3.分析
4·OJ代码
cpp
string minWindow(string& s, string& t)
{
//哈希表+滑动窗口
int hash1[128] = {0};
int hash2[128] = {0};
//开128空间而不开52个空间:涉及到大写字母的问题,在转化为下标的时候,存在溢出或者负数的情况
int kind = 0;//记录t里面有效(非首次出现的字符就不在进记录)字符的个数
for(auto it: t)
if(hash1[it]++ == 0)//只要是首次出现,kind就++
kind++;
int count = 0;//记录窗口内有效的字符个数
int start = -1;//记录子串的起始位置
int sz = INT_MAX;
for(int left = 0,right = 0;right < s.size();++right)
{
//进窗口
char in = s[right];
if(++hash2[in] == hash1[in])
count++;//有效字符
//出窗口
while(count == kind )
{
if(right-left+1 < sz)
{
sz = right-left+1;//最小区间的更新
start = left;//位置的更新
}
//元素删除
char out = s[left++];
if(hash2[out]-- == hash1[out])
count--;//删除有效字符
}
}
return start == -1 ? string():s.substr(start,sz);
}
手搓了这么久的代码,来看看运行结果咋样吧~~~