题目如下:
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的某个变位词。换句话说,第一个字符串的排列之一是第二个字符串的子串 。
本人头脑比较笨,一开始的想法肯定是暴力法。首先因为s1字符串的任何一个排列都有可能是子串,所以要将所有s1字符串的排列可能罗列,再逐一与s2字符串进行对比。刚开始头脑有这个想法的时候,我就开始打自己脑袋了,这中复杂度怎么可能是人写的出来的呢???
然后开始想如何优化,首先字符串是一个个字符型数据的连接,他们可能原本是有一定的顺序的,如果搜索相同顺序的字符串可以用KMP算法,但是本题不是。接下来既然不追求顺序的一致性,可以用无序的HashMap进行统计每个字符出现的次数,Key为字符,Value为出现的次数,然后线性遍历一轮s2进行统计就可以。在遍历的时候,先记录遍历开始的位置start,如果碰到HashMap中有的字符,就让HashMap中的该字符出现的次数-1,如果出现的次数为<0,则代表s2串中多出现了该字符,或者下一个字符不是HashMap中的字符,则重新从s2字符串起始位置的,即start+1开始进行下一次循环。
public boolean checkInclusion(String s1, String s2) {
HashMap<Character, Integer> dic = new HashMap<>();
for (int i = 0; i < s1.length(); i++) {
dic.put(s1.charAt(i),dic.getOrDefault(s1.charAt(i),0)+1);
}
HashMap<Character, Integer> dicTmp = new HashMap<>(dic);
int len = s1.length();
int start = 0;
for (int i = 0; i < s2.length(); i++) {
if (!dicTmp.containsKey(s2.charAt(i))){
dicTmp = new HashMap<>(dic);
len = s1.length();
}else {
dicTmp.put(s2.charAt(i),dicTmp.get(s2.charAt(i))-1);
len--;
if (len == 0){
for (Integer value:dicTmp.values()){
if (value != 0){
start++;
i = start-1;
len = s1.length();
dicTmp = new HashMap<>(dic);
break;
}
}
if (len == 0) return true;
}
}
}
return false;
}
这样的算法虽然在原理上是正确的,可是复杂度太高。HashMap是key-value对应的关系,那么,是否可以不依靠HashMap创建的key,利用数组原有的key进行统计呢?
//维护两个数组
int[] arr1 = new int[26];
int[] arr2 = new int[26];
//将两个数组初始化
for (int i = 0; i < s1.length(); i++) {
arr1[s1.charAt(i) - 'a']++;
arr2[s2.charAt(i) - 'a']++;
}
利用s1.charAt(i) - 'a'这样,将26个字母,利用计算机原本的顺序进行存储,每个位置上的值即为该字符出现的次数。不得不佩服这样的想法,很是巧妙的节省了空间。这样就初始化就得到了两个字符串的各个字符的统计个数,接下来只要比较两个数组是否相等就可以了。可以利用Arrays.equals();这个方法进行判断。在此同时,让窗口滑动起来,每次让这个数组添加后一个字符,删除最前一个字符,滑动到最后可以得出结果,代码如下:
public boolean checkInclusion(String s1, String s2) {
int[] arr1 = new int[26];
int[] arr2 = new int[26];
//避免异常情况推出
if (s1.length()>s2.length()) return false;
//将两个数组初始化
for (int i = 0; i < s1.length(); i++) {
arr1[s1.charAt(i) - 'a']++;
arr2[s2.charAt(i) - 'a']++;
}
//先判断两个是否相等
if (Arrays.equals(arr1,arr2)) return true;
//滑动窗口进行移动
for (int i = s1.length(); i < s2.length(); i++) {
//s2尾新添加一个,头删除一个
arr2[s2.charAt(i)-'a']++;
arr2[s2.charAt(i-s1.length()) - 'a']--;
if (Arrays.equals(arr1,arr2)) return true;
}
return false;
}
个人觉得这种问题的关键在于同化性,找出问题的共性,做这个题目之前我也做过类似的,数组中连续整数的和为k的题目,当时计算的都是数字,做题的方式比较简便。但现在问题来到了字符串中,可以通过上述的方法将字符串也转换成数字进行处理,这点就很巧妙。同样的是,不同类型的数据是否可以向简单化的数据类型进行转化,先化繁为简,再从简单的问题中得出本质,我觉得是这题的收获所在。