题目描述:
给定两个字符串 s1
和 s2
,写一个函数来判断 s2
是否包含 s1
的某个变位词。
换句话说,第一个字符串的排列之一是第二个字符串的 子串 。
我的想法是使用滑动窗口:
先用一个字典存放s1中的字符和字符个数,然后用滑动窗口来检验s2中是否有s1的变式。
代码如下:
public class Solution {
public bool CheckInclusion(string s1, string s2) {
Dictionary<char,int> S1 = new Dictionary<char,int>();
int Window = s1.Length;
int flag = 4;
if(Window>s2.Length) return false;
//用字典存储S1中的字符和字符个数
for(int i = 0;i<s1.Length;i++){
if(!S1.ContainsKey(s1[i])) S1.Add(s1[i],1);
else S1[s1[i]]++;
}
//存储S2中的字符和字符个数
for(int j = 0;j<s2.Length-Window+1;j++){
Dictionary<char,int> S2 = new Dictionary<char,int>();
for(int m = 0;m<Window;m++){
if(!S2.ContainsKey(s2[j+m])) S2.Add(s2[j+m],1);
else S2[s2[j+m]]++;
}
foreach(var item in S1.Keys){
if(!S2.ContainsKey(item)){
flag = 1;
break;}
else if(S1[item]!=S2[item]){
flag = 2;
break;}
else if(S1[item]==S2[item]) flag = 0;
}
if(flag==0) break;
}
return flag==0?true:false;
}
}
可以说是好不容易终于通过了,但是通过的效果却不是很好
才PK掉了8%的人🤔 不过的确我这个滑动窗口就是老老实实地一步一步地走,效率的确不高,但是要怎么才能效率更高呢?
看了一下解析,可以优化的地方是不要每次都整个字典地遍历,因为每次都只向右移动一个数,所以相当于是减少了一个数,再增加一个数,在检查的时候只需要检查这两个数的情况就可以了,这就大大减少了很多重复的计算量
此外还可以引入双指针的思想,因为上面的想法是固定长度,看s2固定窗口中的数和s1的数之间的关系,即保证区间长度为 n的情况下,去考察是否存在一个区间使得 s1中字符和字符数组和固定窗口中的一致。反过来,还可以在保证 s1中字符和字符数组和s2子串中的一致的情况下,去考察是否存在一个区间,其长度恰好为 n。
代码如下:
public class Solution {
public bool CheckInclusion(string s1, string s2) {
Dictionary<char, int> need = new Dictionary<char, int>();
Dictionary<char, int> window = new Dictionary<char, int>();
foreach (char c in s1)
{
if (need.ContainsKey(c)) need[c]++;
else need.Add(c, 1);
}
int left = 0;
int right = 0;
int valid = 0;
while (right < s2.Length)
{
// c 是将移入窗口的字符
char c = s2[right];
// 右移窗口
right++;
if (need.ContainsKey(c))
{
if (window.ContainsKey(c)) window[c]++;
else window.Add(c, 1);
if (window[c] == need[c]) valid++;
}
// 判断左侧窗口是否要收缩
while (right - left >= s1.Length)
{
// 在这里判断是否找到了合法的子串
if (valid == need.Count) return true; //字典.Count是字典元素计数
char d = s2[left];
left++;
// 进行窗口内数据的一系列更新
if (need.ContainsKey(d))
{
if (window[d] == need[d])
valid--;
window[d]--;
}
}
}
return false;
}
}
这种思想的效率有明显的提高