变位词
变位词是与字符串相关算法题经常出现的概念,所谓变位词是指组成各个单词的字母及每个字母出现的次数完全相同,只是字母排列顺序不同。例如:“pots”,“stop”,“tops” 就是一组变位词.
字符中的变位词
输入字符串s1和s2,如何判断字符串s2中是否包含字符串s1的某个变位词?如果字符串s2中包含字符串s1的某个变位词,则字符串s1至少有一个变位词是字符串s2的子字符串。
假设两个字符串中只包含英文小写字母。
例如,字符串s1为"ac",字符串s2为 “decaf”,由于字符串s2中包含字符串s1的变位词“ca”,因此输出为true。
如果字符串s1 为“ab”,字符串s2为“dgcaf”,则输出为false。
思路
看到题目第一反应竟然是用暴力求解:求出所给长度为n的字符串s2的所有排列组合,然后用s1去逐个配对,这样也不是不可以,但是就会形成时间复杂度不低于O(n!)的情况,那这就背离了算法的初衷并不是万物都可暴力。
观察题目:
- 给定了字符串中为小写字母,及字符串中的元素个素为有限的26个。
- 变位词一定为相同的元素组成
因此我们可以借助数组实现哈希表:
- 记录元素以及出现的次数
- 同时用双指针的方法匹配数据(变位词的元素在相同的范围内变换)
实现:
- 遍历子元素s1,在开辟的26个容量的数组对应位置上+1,s1完成初始化,
- 根据s1的长度遍历s2,在开辟的26个容量的数组对应位置上-1,s2至s1的长度段完成初始化
- 接着第2步继续遍历s2,继续完成s2的初始化
- 同时在s2中保持s1的长度对删掉的元素进行+1
- 如果遍历完s2发现哈希容器为0那就满足要求
实现
public class AnagramStr01 {
public boolean checkInclusion(String s1, String s2) {
if (s2.length() < s1.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 (areAllZero(counts)){
return true;
}
//根据s1的长度在s2遍历验证
for (int i = s1.length(); i <s2.length(); ++i) {
counts[s2.charAt(i)-'a']--;
counts[s2.charAt(i-s1.length())-'a']++;
//每一步都在验证
if (areAllZero(counts)){
return true;
}
}
return false;
}
private boolean areAllZero(int[] counts) {
for (int count : counts) {
if (count!=0){
return false;
}
}
return true;
}
public static void main(String[] args) {
AnagramStr01 anagramStr01 = new AnagramStr01();
boolean b = anagramStr01.checkInclusion("ac", "dgcaf");
System.out.println(b);
}
}
验证
-
容器初始化:
-
此时指针在s2上到达【到
’c‘
删’d‘
】后:这时s2的校验字符为【gc
–>ac
】 -
此时指针向后移动,到达:【到
’a‘
删’g‘
】后,这是字符串校验的字符为【ca
-->ac
】完成遍历!
分析:
- 基于双指针和哈希表的算法只需要各扫描s1和s2一次即可,如果字符串长度为m和n的话,那么时间复杂度则为O(m+n),完胜O(n!)