1. 题目描述
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。
示例1:
输入: s1 = “ab” s2 = “eidbaooo”
输出: True
解释: s2 包含 s1 的排列之一 (“ba”).
示例2:
输入: s1= “ab” s2 = “eidboaoo”
输出: False
注意:
输入的字符串只包含小写字母
两个字符串的长度都在 [1, 10,000] 之间
来源:力扣(LeetCode)
原题传送门:link.
2. 考察知识点
hash表,滑动窗口
3. 思路
- 由于长度固定,所以可以使用滑动窗口,由于时排列不同,如何判断呢?
- 如果说S1是S的排列之一,就等价于它们每个字母出现的频率是相同的。所以可以建一个数组,数组索引对应字符,数组内的值记录字符出现的频率。
- 当s2的滑动窗口内的字符串与s1的字符串字符出现频率相同时,说明匹配(可以用一个循环来对比)。
- 如果不相同,那么滑动下去(字符的增加删除对应了数组内的频率增减)。
链接:link.
来源:力扣(LeetCode)
4. 代码(Java)
public class Solution {
public boolean checkInclusion(String s1, String s2) {
//
if (s1.length() > s2.length())
return false;
//滑动字符串对应的数组初始化
int[] s1map = new int[26];
int[] s2map = new int[26];
for (int i = 0; i < s1.length(); i++) {
s1map[s1.charAt(i) - 'a']++;
s2map[s2.charAt(i) - 'a']++;
}
//滑动s2字符串上的窗口尝试找到同频率字符串
for (int i = 0,j= s1.length(); j < s2.length(); i++,j++) {
if (matches(s1map, s2map))
return true;
s2map[s2.charAt(j) - 'a']++;
s2map[s2.charAt(i) - 'a']--;
}
return matches(s1map, s2map);
}
//对比字符频率是否相同
public boolean matches(int[] s1map, int[] s2map) {
for (int i = 0; i < 26; i++) {
if (s1map[i] != s2map[i])
return false;
}
return true;
}
}
5. 时间及空间复杂度
6. 改进思路
-
上一种方法可以优化,如果不是比较每个更新的 s2maps2map 的哈希表的所有元素,而是对应于 s2s2 考虑的每个窗口,我们会跟踪先前哈希表中已经匹配的元素数量当我们向右移动窗口时,只更新匹配元素的数量。
-
为此,我们维护一个 count 变量,该变量存储字符频率对应的个数。当两个字符串字母频率完全不同时count =0;当两个字符串字母频率完全相同时count =26;s1内字符频率是相同的。当我们在s2内滑动窗口时,如果增加最后一个字符ch_r( 对应数组的改变为:s2map[r]++)会导致两种情况:
- 原来两字符串字符ch_r频率不相同,但是增加后频率相同(s2map[r] == s1map[r]),此时count++;
- 原来两字符串字符ch_end频率相同,但是因为增加后频率不相同 (s2map[r] == s1map[r] + 1),此时count–;
注意这里一定是因为增加引起的频率由相同到不相同的转变,才count–
如果扣除第一个字符ch_i( 对应数组的改变为:s2map[i]–)会导致以下情况:
- 原来两字符串字符ch_i频率不相同,但是扣除后频率相同(s2map[i] == s1map[i]),此时count++;
- 原来两字符串字符ch_end频率相同,但是因为扣除后频率不相同 (s2map[i] == s1map[i] - 1),此时count–;
注意这里一定是因为扣除引起的频率由相同到不相同的转变,才count–
- 如果在移动窗口后,countcount 的计算结果为26,则表示所有字符的频率完全匹配。所以,我们立即返回一个True。
public class Solution {
public boolean checkInclusion(String s1, String s2) {
if (s1.length() > s2.length())
return false;
int[] s1map = new int[26];
int[] s2map = new int[26];
for (int i = 0; i < s1.length(); i++) {
s1map[s1.charAt(i) - 'a']++;
s2map[s2.charAt(i) - 'a']++;
}
int count = 0;
for (int i = 0; i < 26; i++)
if (s1map[i] == s2map[i])
count++;
for (int i = 0; i < s2.length() - s1.length(); i++) {
int r = s2.charAt(i + s1.length()) - 'a', l = s2.charAt(i) - 'a';
if (count == 26)
return true;
s2map[r]++;
if (s2map[r] == s1map[r])
count++;
else if (s2map[r] == s1map[r] + 1)
count--;
s2map[l]--;
if (s2map[l] == s1map[l])
count++;
else if (s2map[l] == s1map[l] - 1)
count--;
}
return count == 26;
}
}
7. 题目升级:s1不重复,如何快速查找?
- 思路1:查找s2中与s1长度相同且字符不重复的子串,然后将所以可能逐一对比,知道找到相同频率的字串。
- 思路2:滑动窗口的过程中判断字符串的子串是否重复,如果重复不对比直接向下滑动。
//1.滑动窗口不动,从前往后检测滑动窗口内第一个字符,使用mount=0计数,当mount=26时,退出,对滑动窗口内的字符串与s1进行比对。
//2.同时在数组内检索每个字符的频率,当频率为1时,mount++,窗口不动。
//3.当遇到频率大于1,滑动窗口的起点至此。mount=0
//对比字符频率是否相同
public class Solution {
public boolean checkInclusion(String s1, String s2) {
//
if (s1.length() > s2.length())
return false;
//滑动字符串对应的数组初始化
int[] s1map = new int[26];
int[] s2map = new int[26];
for (int i = 0; i < s1.length(); i++) {
s1map[s1.charAt(i) - 'a']++;
s2map[s2.charAt(i) - 'a']++;
}
int x=0;
int count=0;
int i = 0,j= s1.length();
//滑动s2字符串上的窗口尝试找到同频率字符串
while(count!=26&&j<s2.length()){
count=0;
x++;
if(s1map[s1.charAt(i)- 'a']==s2map[s1.charAt(i)- 'a']){
count++;
}else{
int y= x-i;
while(y>0){
y--;
s2map[s2.charAt(j) - 'a']++;
s2map[s2.charAt(i) - 'a']--;
j++;
i++;
}
}
}
if(count==26) {
return true;
}else{
return false;
}
}
}