题目描述
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:
- 字母异位词指字母相同,但排列不同的字符串。
- 不考虑答案输出的顺序。
示例 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” 的字母异位词。
思路分析
滑动窗口算法。
- 左右指针,初始化都为0,把[left,right]当成一个窗口;
- 不断增大right,直到窗口内的子串符合要求;
- 不断增大left,缩小窗口直到窗口内子串不符合要求,在缩小left时记录结果;
- 直到right到达字符串串尾。
对于如何判断是否符合要求,建立两个哈希表,一个needs记录pattern子串中字符出现次数,一个windows记录当前窗口子串中字符出现次数。用一个match记录符合规则的字符数。
当match等于needs的长度时,表明满足条件,开始缩小窗口。每次缩小时,都要再次判断条件,更新match。
注:Integer范围是-128-127,在范围内用缓存值,超过这个范围会new一个对象,所以得用equals()比较地址
代码实现
public static List<Integer> findAnagrams(String s, String p) {
List<Integer> list = new ArrayList<>();
if (s.length() == 0 || p.length() == 0) {
return list;
}
int left = 0, right = 0, match = 0;
Map<Character, Integer> windows = new HashMap<>();
Map<Character, Integer> needs = new HashMap<>();
//子串可能出现多次
for (char c : p.toCharArray()) {
Integer times = needs.getOrDefault(c, 0);
needs.put(c, times + 1);
}
char[] chars = s.toCharArray();
while (right < chars.length) {
char c1 = chars[right];
//模式串有这个字符
if (needs.containsKey(c1)) {
Integer times = windows.getOrDefault(c1, 0);
windows.put(c1, times + 1);
//一个字符匹配完成
if (needs.get(c1).equals(windows.get(c1))) {
match++;
}
}
right++;
//窗口已经符合要求
while (match == needs.size()) {
if (right - left == p.length()) {
//更新list,存left下标
list.add(left);
}
//存完就向右移动left
//先检查left位置上的字符是不是要的字符
char c2 = chars[left];
if (needs.containsKey(c2)) {
Integer tmp = windows.get(c2);
tmp--;
//如果c2字符出现的次数少于模式串中出现的次数
if (tmp < needs.get(c2)) {
match--;
}
//tmp为0就删掉字符
if (tmp == 0) {
windows.remove(c2);
} else {
windows.put(c2, tmp);
}
}
left++;
}
}
return list;
}