题目描述
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。
示例1:
输入: s1 = “ab” s2 = “eidbaooo”
输出: True
解释: s2 包含 s1 的排列之一 (“ba”).
示例2:
输入: s1= “ab” s2 = “eidboaoo”
输出: False
思路
第一眼看到这个题 暴力解法在脑海里面就散开了
直接罗列s1的所有字符的排列组合,然后每个组合都去跟s2匹配,看看是否在s2中存在。
只要有一个存在,就返回true
然后就想 怎么能够确保罗列完所有的组合呢 ,然后卡住了。。。。。
后面实在不知道咋做,就开始看答案。发现了 ”滑动窗口“ 解法
还要想明白一个事:
重点
不再是去举例s1的每一种组合可能,而是判断s2的一个序列,如果该子序列中 每个字符的数量与s1中的字符数量相同,那么就代表符合题意。
- 例: s1:ab s2:bacd
那么s2的 ”ba" 子序列 统计字符数量为 a:1, b:1, s1中的字符数量统计为:a:1, b:1
这就是 **s2包含s1的排列
现在要做的就是 从s2的最左边,保持一个s1长度的窗口,每次+1,依次滑动到s2的最后,只要滑动过程中出现子序列,与s1的字符统计数量完全相同,即代表找到了符合题意的s2子序列 ,返回true
选择数据结构:
- hashMap
统计字符数量,一般想到的肯定是map,直接两个hashMap,第一个map1用来存储s1的字符以及各字符的数量,第二次map2用来存储s2每次滑动窗口所获得的子序列的字符以及各字符的数量。
那么代码就是下面这个样子:
public boolean checkInclusion(String s1, String s2) {
int n1 = s1.length(), n2 = s2.length();
if(n1 > n2){
return false;
}
Map<Character, Integer> map1 = new HashMap<>();
Map<Character, Integer> map2 = new HashMap<>();
for(int i = 0; i < n1; i++){
if(map1.containsKey(s1.charAt(i))){
map1.put(s1.charAt(i), map1.get(s1.charAt(i)) + 1);
}else{
map1.put(s1.charAt(i), 1);
}
if(map2.containsKey(s2.charAt(i))){
map2.put(s2.charAt(i), map2.get(s2.charAt(i)) + 1);
}else{
map2.put(s2.charAt(i), 1);
}
}
if(map1.equals(map2)){
return true;
}else{
for(int i = 0; i < n2-n1; i++){
map2.put(s2.charAt(i), map2.get(s2.charAt(i)) - 1);
if(map2.get(s2.charAt(i)) < 1){
map2.remove(s2.charAt(i));
}
if(map2.containsKey(s2.charAt(i + n1))){
map2.put(s2.charAt(i + n1), map2.get(s2.charAt(i + n1)) + 1);
}else{
map2.put(s2.charAt(i + n1), 1);
}
if(map1.equals(map2)){
return true;
}
}
return false;
}
}
作者:dixinfan-2
链接:https://leetcode-cn.com/problems/permutation-in-string/solution/java-shuang-hashmaphua-dong-chuang-kou-by-dixinfan/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这里我用的别人的答案,可以看出,逻辑很清楚,但是就是代码十分冗长,但是由于使用了两个hashMap,所以空间复杂度比较高。
- 数组
第二种解法就是利用数组来存储每个字符的数量:具体解释可以在代码中看到
public static boolean checkInclusion(String s1, String s2) {
int len1 = s1.length();
int len2 = s2.length();
if(len1 > len2)
return false;
int[] array1 = new int[26];
int[] array2 = new int[26];
for(int i = 0; i < len1; i++){
array1[s1.charAt(i) - 'a']++;
array2[s2.charAt(i) - 'a']++;
}
for(int i = len1 ; i < len2; i++) {
if(isEqual(array1, array2))
return true;
//滑动窗口, ++是将所滑过的字母对应的数组中的元素+1,
//--是当数组滑动过了当前的位置,并没有找到与s1完全相同的序列。
// 所以需要删除前面添加进数组中的元素(为了保持整个数组完全相同)
/**
*例子:s1:ab s2: cdabef 经过第一个for循环 array1={1,1,0,0,.....} array2={0,0,1,1,0,0,....}
* 1.滑动窗口第一次进入s2,数组array2变为{1,0,0,1,0,0,.....} (s2 中第一个字符'c'对应的数组元素变为0)
* 此时进入下一次循环,判断array1 array2不相等,继续
* 2.滑动窗口滑动一个位置,保持len1的长度不变, 数组array2变为{1,1,0,0,0,....} (s2中第二个字符'd'对应的数组元素变更为0)
* 此时进入下一次循环,判断array1,array2相等,返回true
* (i-len1) 是为了维护窗口左边界, i是为了维护窗口右边界 每一次滑动数组不相等,则滑动一次窗口,左边界+1,右边界也+1.
*/
--array2[s2.charAt(i - len1) - 'a'];
++array2[s2.charAt(i) - 'a'];
}
return isEqual(array1, array2);
}
private static boolean isEqual(int[] array1, int[] array2) {
for(int i = 0; i < 26; i++){
if(array1[i] != array2[i])
return false;
}
return true;
}
可以看出第二个办法的空间复杂度一下就降到了o(1) 。
这道题学到了很多,比如 滑动窗口解法 还有就是尽量利用简单的数据结构来解决问题,优化不是一点半点。