题目:
输入字符串s1和s2,如何判断字符串s2中是否包含字符串s1的某个变位词(组成各个单词的字母以及每个字母出现的次数完全相同,只是字母的排列的顺序不同例如pots和stop)?如果字符串s2中包含字符串s1的某个变位词,则字符串s1至少有一个变位词是s2的子字符串。假设两个字符串中只包含英文小写字母。例如,字符串s1为“ac”,字符串s2为“dgcaf”,由于字符串s2中包含字符串s1的变位词“ca”,因此输出为true。如果字符串s1为“ab”,字符串s2为“dgcaf”,则输出为false。
回顾:
刚刚到新的章节字符串,来回顾一下String类型常用的函数,charAt函数返回指定下标字符,compareTo按照字典顺序比较字符串,equals判断两个字符串的长度和内容是否相同,indexOf返回字符串中某个字符或子字符串首次出现的下标位置,lastIndexOf返回字符串中某个字符或子字符串最后出现的下标位置,length返回字符串长度,split按字符串指定的分隔符进行分割,substring根据下标截取字符串,toLowerCase/toUpperCase将字符串中的所有大写或小写字母改造成小写或大写字母。
分析:
变位词的特点是长度必须一定相同,其次组成变位词的字母集合一定相同,并且每个字母出现的次数也相同。
暴力法可以解决,求出s1字符串的所有排列,然后判断每个排列是不是字符串s2的子字符串,如果一个字符串有n个字符,那么它一个有n!个排列,因此时间复杂度不会低于O(n!),时间复杂度太高不可取。
可以采用哈希表统计字符串包含的字母以及每个字母出现的次数,具体操作如下图例子所示。
解决这种问题的思路就是利用好数组,完全可以利用两个ASCII值相减得到数组下标,数组下标对应的就是那26个字母,然后通过规律添加元素字母对应的次数值减1,减少元素字母对应的次数值加1,如存在数组所有值都等于0的时,说明存在变位词。
代码如下:
public class CheckInclusion {
public static void main(String[] args) {
String s1 = "ca";
String s2 = "dgacf";
boolean b = checkInclusion(s1, s2);
System.out.println(b);
}
public static boolean checkInclusion(String s1,String s2){
if (s1.length()>s2.length()){
return false;
}
int[] counts = new int[26];
for (int i = 0; i < s1.length(); i++) {
counts[s1.charAt(i)-'a']++;
counts[s2.charAt(i)-'a']--;
}
if (allAllZero(counts)){
return true;
}
for (int i = s1.length(); i <s2.length() ; i++) {
counts[s2.charAt(i)-'a']--;
counts[s2.charAt(i-s1.length())-'a']++;
if (allAllZero(counts)){
return true;
}
}
return false;
}
private static boolean allAllZero(int[] counts) {
for (int count : counts) {
if (count !=0){
return false;
}
}
return true;
}
}
该算法利用了两个for循环,扫描字符串s1和s2各一次,如果两者长度分别是m和n,那么算法时间复杂度O(m
+n),空间复杂度由于建立的是固定常数大小的数组,所以空间复杂度为O(1)。