题目描述:
给定两个字符串 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" 的异位词。
题解:
使用滑动窗口的通用思想:
设置左右两个指针,分别指向当前互动窗口的左右边界,初始时都置为0。
遍历时,右移right指针,圈入一个新的元素到窗口中,进行一系列的数据更新操作(更新什么数据);
每次右移完都判断一下现在是否需要开始缩小窗口(判断条件是什么),缩窗口的时候是从左边开始缩,缩小之后再进行数据的更新(更新什么数据);
循环往复执行右移窗口,缩小窗口,最终处理完right指针指向的最后一个元素后结束循环处理。
还需要考虑问题:什么时候进行符合题目要求的结果值记录?
套用到这道题中:
考虑什么时候开始缩小窗口?
对于这道题来说,只要现在窗口大小等于p.length()就可以开始缩小窗口了;
考虑右移窗口的时候需要更新什么数据?
由于我们需要判断的是当前窗口中的字符串是否是一个p的异位词,即当前窗口中的每一种“有用”元素的数量是否和p中每一种元素数量相对应,并且p中的元素种类和窗口中的元素种类也是一致的,“有用”:p是abc,但是现在窗口中是sbc,那么s的数量我们其实不需要记录。因此需要一个存储当前窗口中每一种元素数量的数据结构,考虑用map实现(这里取名windows);
由于我们还需要知道p中元素每一种类是否在当前窗口中都有记录,并且在当前窗口中找齐了全部p中每种元素的全部数量,所以我们可以设置一个变量valid,记录当前窗口中已经完整找出的p中元素种类的个数,比如,现在p中a有3个,b 2 个,c 1 个 ,窗口在右移的过程中,我不断在windows中记录圈进窗口的每种“有效”元素的个数,假如某一次右移之后,更新完数据发现刚好windows.get(a)==3,那么说明当前窗口已经完整的圈出全部p中的a了,就令valid++,相当于已经完成了在窗口中圈异位词工作的1/3的进度(剩下2/3进度就是圈出b和c),当b和c也圈好了,那么valid更新为3。
考虑什么时候进行符合要求的结果记录?
在缩窗口之前,当前窗口的大小等于p的大小,此时就能来判断当前窗口圈住的内容是否是一个满足题目要求的异位词,具体判断过程就是看valid是否等于p中种类数,是的话,就说明当前窗口记录的是一个异位词。
代码实现:
public static List<Integer> findAnagrams(String s, String p) {
int left = 0,right = 0;
Map<Character,Integer> need = new HashMap<>();
/*windows的作用:
在当前滑动窗口中记录每种字符的个数,用来判断现在need中某一种字符是否已经全部在滑动窗口中圈入,
如果全部圈入,vails进度就可以加1。
在进行缩小窗口进行数据更新的时候也需要对该windows值做更新
*/
Map<Character, Integer> windows = new HashMap<>();
int vaild = 0;
List<Integer> list = new ArrayList<>();
//初始化need
for (int i = 0; i < p.length(); i++) {
char c = p.charAt(i);
need.put(c,need.getOrDefault(c,0)+1);
}
while (right<s.length()){
char c = s.charAt(right);
right++;
/*进行窗口内数据的更新*/
//如果现在遍历到的元素是p中需要的,更新windows值
if(need.containsKey(c)){
windows.put(c,windows.getOrDefault(c,0)+1);
//如果现在windows中某一种字符的数量已经达到need中该字符的数量,说明在wind中圈入该字符的任务已经完成,vaild++
if(need.get(c).equals(windows.get(c))){
vaild++;
}
}
/*判断窗口是否需要缩小*/
//每当right-left==p.length的时候,即当前滑动窗口圈满3个元素
if(right-left==p.length()){
//缩小窗口之前看看现在的进度是否已经全部结束
if(vaild==need.size()){
list.add(left);
}
char d = s.charAt(left);
left++;
/*进行一系列数据更新*/
if(need.containsKey(d)){
if(windows.get(d).equals(need.get(d))){
vaild--;
}
windows.put(d,windows.get(d)-1);
}
}
}
return list;
}
知识点:
windows.put(c,windows.getOrDefault(c,0)+1);
之前49中遇到过,但是我还是没记住,再写一遍,加深印象。