Q:给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的某个变位词。假设字符串全是小写字母
看到这道题,最吸引我的还是【变位词】
你给翻译翻译,什么叫变位词?
变位词,即字母位置变换但是数量不变的字符串。
其实简单来说 ,如果将字符串视为数组的话,这就是在问你s1 数组内是否存在s2数组的子集?
针对字符串类的问题,用我擅长的方式就是类比,字符串可以视为多个字母构成的数组,那么但凡是数组类的问题,我们都可以使用【双端指针】or【哈希+遍历】的方式去解决 。
回到问题上来 ,如果存在变位词,那么也是就说s1里存在的字母,在s2中也出现了。
也就是说存在了映射关系,既然有了映射关系那必然存在一张映射表进行维护。
那么假设,字母作为key,value为在字符串内出现的次数(默认0)。
我们先使用s1对映射表进行初始化,目的是统计出s1中字母出现的频率。
接下来,再使用s2字符串对映射表进行操作,当出现了同样的字母则扣减统计次数。
顺利的话,映射表内value=0的字母就是共有的字母了。
但如果不顺利的话,就需要利用【滑动窗口】的想法(窗口大小为短字符串的长度)来进行遍历匹配得到最终结果。
有一点比较特殊的是,题目限定了字符串均为小写字母,相当于给定了映射表的大小。
同时由于全部是小写字母,那么我们利用小写字母的ascii码来进行计算,因为小写字母的ascii码是递增的。
eg:当用‘a’ - ‘a’,由于同样的字母,则结果0,而0在数组中指向了首个元素;
当用‘b’ - ‘a’,由于相邻字母ascii码递增,所以差值为1,而1在数组中指向了第2个元素,正好 是当前字母在字母表中的顺序。
综上所述,代码如下:
public static void main(String[] args) {
String s1 = "ac";
String s2 = "dgcaf";
System.out.println(check(s1,s2));
}
public static boolean check(String s1, String s2 ){
//init初始化容器
int[] counts = new int[26];
for (int i = 0; i < s1.length(); ++i) {
//这里想表达的是 利用字母的ascii码进行做差,差值即索引位置,如果存在则转换成1
//eg:a的ascii码是97,b的ascii码是98;如果a存在则索引0的值为1,否则是0
counts[s1.charAt(i)-'a']++;
//这里想表达的是,如果在S2中找到了同样位置的索引,则将容器值减1,一般结果为0 or -1
counts[s2.charAt(i)-'a']--;
}
if (allZero(counts)){
return true;
}
//这里使用了滑动窗口的思想,窗口大小为s1的长度
//i指定s1的长度,想表达的是指定了窗口的初始化位置
for (int i = s1.length(); i < s2.length(); i++) {
//界定窗口左边界右移动 dgcaf中的 'd' 已经重新补位置
counts[s2.charAt(i-s1.length())-'a']++;
//界定窗口右边界右移动 dgcaf中的 'c' 已经重新扣减
counts[s2.charAt(i)-'a']--;
//以上操作 完成了dgcaf中 窗口从[dg]caf -> d[gc]af的变化 (初始化窗口是[dg]是因为初始化时刻 s1的长度导致)
//伴随着遍历,形成了窗口的滑动趋势,从s2的左端一直滑动到s2的右端
if (allZero(counts)){
return true;
}
}
return false;
}
public static boolean allZero(int[] counts){
for (int value: counts) {
if (value!=0){
return false;
}
}
return true;
}