438. 找到字符串中所有字母异位词
难度 中等
给定两个字符串 s
和 p
,找到 s
中所有 p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
示例 1:
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
示例 2:
输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。
提示:
1 <= s.length, p.length <= 3 * 104
s
和p
仅包含小写字母
题解
这道题就是寻找字符串P在S中的排列,刚开始想的是遍历,失败就回溯,简单来说就是暴力,但是时间复杂度堪忧。就是通过统计字母数组,比较两个统计字母数组相同。
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> ans = new ArrayList<Integer>();//结果数组
int[] count = new int[26];//记录P中所有字母出现的次数
int plen = p.length();//P的长度
for(int i = 0; i < plen; i++){//统计P字母出现的次数
count[p.charAt(i) - 'a']++;
}
int slen = s.length();//s的长度
for(int i = 0; i < slen; i++){//遍历s
if(count[s.charAt(i) - 'a'] > 0){//当前字母存在P字符串中
int[] temp = (int[])Arrays.copyOf(count, count.length);//复制p的统计数组
int flag = 1;//失败标记
int j = 0;//遍历长度
for(; j < plen && j + i < slen; j++){
if(--temp[s.charAt(i + j) - 'a'] < 0){//如果s的字符串统计超过了p的字符串统计,枚举失败
if(count[s.charAt(i + j) - 'a'] == 0){//如果s的字符在p中不存在,不用回溯,直接跳到下个字符
i += j - 1;
}
flag = 0;
break;
}
}
if(j == plen && flag == 1){//枚举成功
ans.add(i);
}
}
}
return ans;
}
}
想不出优化的办法,看了一下官方题解,不用回溯的方法确实秒。通过differ来看字符串之间差多少个字母。那这个differ是怎么其效果的,我也想了很久。想不通描述,就从代码入手。
这里的辅助数组需要理解一下,count是一个字符记录数组。但是不是记录了字母出现多少次,而是记录了s和p在某个字符之间差多少(我定义为字母差)。aba和abc,这两个字符串相差1个字符,differ就相差1。详细的解析需要结合代码,全部写在代码上了,可以结合代码给的理解理解一下。
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int slen = s.length();
int plen = p.length();
if(slen < plen){
return new ArrayList<Integer>();
}
List<Integer> ans = new ArrayList<Integer>();
int[] count = new int[26];//辅助数组
for(int i = 0; i < plen; i++){
++count[s.charAt(i) - 'a'];//加上s字符串的字母出现的次数
--count[p.charAt(i) - 'a'];//减去p字符串的字母出现的次数
}
int differ = 0;//初始化为0
for(int j = 0; j < 26; j++){//遍历26个字母
if(count[j] != 0){//如果某个字母差别不为0
++differ;//字母差加一
}
}
if(differ == 0){//如果没有字母差
ans.add(0);
}
for(int i = 0; i < slen - plen; i++){
//遍历s字符串,这里比较抽象,要理解在做什么
//要理解这里在做什么,先理解(s.charAt(i) - 'a')和(s.charAt(i + plen) - 'a')
//s.charAt(i) - 'a'是以i为开始的字母(就是要删除的首字母)
//s.charAt(i + plen) - 'a'是以i开始,长度plen结束的字母(就是要新添加进来的尾字母)
//上面我们已经做了从0开始,plen长度的字符串的匹配
//然后我们移动时去除第一个字母,也就是这里的s.charAt(i);在尾部加上一个字母,也就是这里的s.charAt(i + plen),这么做为了保持长度
//此时我们要移动字符串进行匹配,删除第一个字母,就是count[s.charAt(i) - 'a']--;
//如果删除的这个字母不一样,字母差减一
// 例如这种情况,s:cabd,p:abd,第一个c字母在字符串p中不存在,然后如果count[c] == 1(这个c字母差只有一个),我们又删除了这个字母c,那我们的字母差就减少了,所以differ--
//如果删除的这个字母是一样的,字母差加一
// 例如这种情况,s:abdc,p:abd,第一个a字母在字符串p中存在,然后count[a] == 0(这个a字母差没有),我们又删去了这个字母a,那我们的字母差就增加了,所以differ++
if(count[s.charAt(i) - 'a'] == 1){
differ--;
} else if(count[s.charAt(i) - 'a'] == 0){
differ++;
}
count[s.charAt(i) - 'a']--;
//上面做了删除首字母操作,为了保证长度,我们需要在末尾加上一个字母,count[s.charAt(i + plen) - 'a']++;
//如果添加的这个字母一样的,字母差减一
// 例如这种情况,s:abdc,p:cbd,添加的C字母在字符串p中存在,然后count[c] == -1(这个C字母差只有一个),我们又添加了这个字母c,那我们的字母差就减少了,所以differ++
//如果添加的这个字母是不一样的,字母差加一
// 例如这种情况,s:abdc,p:abd,添加的c字母在字符串p中不存在,然后count[c]=0,我们又添加了这个字母c,那我们的字母差就增加了,所以differ++
if(count[s.charAt(i + plen) - 'a'] == -1){
differ--;
}else if(count[s.charAt(i + plen) - 'a'] == 0){
differ++;
}
count[s.charAt(i + plen) - 'a']++;
//如果删去一个,添加一个,字母差为0 ,则是我们想要的答案。
if(differ == 0){
ans.add(i + 1);
}
}
return ans;
}
}