简介
题目链接:https://leetcode.cn/problems/find-all-anagrams-in-a-string/description/
解决方式:字符串 + 滑动窗口、哈希表 / 数组
这是作者学习众多大神的思路进行解题的步骤,很推荐大家解题的时候去看看题解里面大佬们的思路、想法!
滑动窗口
思路:这题的思路与76.最小覆盖子串题目类似,都是使用滑动窗口然后通过哈希表或数组第三方数据结构判断滑动窗口的扩大、收缩时机。不一样的是,收缩条件变为滑动窗口的长度等于目标的长度。在这种情况下,valid 变量等于所需字符种类数,相当于固定长度(长度等于目标)的滑动窗口包含了所有目标字符种类且满足数量,该窗口字串就是目标的排列或字母异味词了。
哈希表
推荐查看 labuladong 大佬的题解!
java
class Solution {
public List<Integer> findAnagrams(String s, String p) {
// 哈希表,所需的字符种类和数量
HashMap<Character,Integer> need = new HashMap();
for(int i = 0; i < p.length(); i++){
char c = p.charAt(i);
need.put(c, need.getOrDefault(c, 0) + 1);
}
// 哈希表,滑动窗口中对于的哈希值
HashMap<Character,Integer> window = new HashMap();
// 双指针
int left = 0;
int right = 0;
// 滑动窗口中满足目标字符的个数
int valid = 0;
// 结果
ArrayList<Integer> list = new ArrayList();
// 迭代
while(right < s.length()){
// 当前字符
char c = s.charAt(right);
// 滑动窗口扩大
right++;
// 判断
if(need.containsKey(c)){
// 当前元素是目标字符,加入滑动窗口对于的字符哈希表中
window.put(c, window.getOrDefault(c, 0) + 1);
if(window.get(c).equals(need.get(c))){
// 滑动窗口中的该字符满足目标
valid++;
}
}
// 滑动窗口保持固定大小移动(缩小)
while(right - left == p.length()){
if(valid == need.size()){
// 大小一致,目标字符种类数量也满足,当前子串就是异位词
list.add(left);
}
// 左边界移动
char cleft = s.charAt(left);
left++;
// 元素为目标字符
if(need.containsKey(cleft)){
if(window.get(cleft).equals(need.get(cleft))){
valid--;
}
window.put(cleft, window.getOrDefault(cleft, 0) - 1);
}
}
}
// 返回结果
return list;
}
}
数组
推荐查看 灵茶山艾府 大佬的题解!
java
class Solution {
// 滑动窗口
// 大体思路是有两个数组,一个代表滑动窗口,一个代表目标字符串,滑动窗口的大小于与目标字符串相同
// 移动滑动窗口,比较两数组是否相同。相同则表示寻找到目标的字母异位体,因为两者的字符、数量相同
public List<Integer> findAnagrams(String s, String p) {
int sLen = s.length(), pLen = p.length();
// 特例判断
if(sLen < pLen){
return new ArrayList<Integer> ();
}
// 初始化数组和结果集
ArrayList<Integer> ans = new ArrayList<>();
int[] sCount = new int[26];
int[] pCount = new int[26];
// 初始化滑动窗口
for(int i = 0; i < pLen; i++){
// 字符与数组索引映射
++sCount[s.charAt(i) - 'a'];
++pCount[p.charAt(i) - 'a'];
}
if(Arrays.equals(sCount, pCount)){
ans.add(0);
}
// 移动滑动窗口
// 此时,i 代表滑动窗口的左边界以及 s 中剩下的字符,即滑动窗口需要移动的次数
for(int i = 0; i < sLen - pLen; i++){
// 去除左边界元素
--sCount[s.charAt(i) - 'a'];
// 滑动窗口向右移动
++sCount[s.charAt(i + pLen) - 'a'];
// 判断
if(Arrays.equals(sCount, pCount)){
// 初始位置为左边界 + 1
ans.add(i + 1);
}
}
return ans;
}
}
数组优化
java
class Solution {
// 滑动窗口优化
// 大体思路是维护一个数组和一个不同变量 differ,取代两个数组的全量比较
public List<Integer> findAnagrams(String s, String p) {
int sLen = s.length(), pLen = p.length();
// 特例判断
if(sLen < pLen){
return new ArrayList<Integer> ();
}
// 初始化数组和结果集
ArrayList<Integer> ans = new ArrayList<>();
int[] count = new int[26];
// 初始化滑动窗口
for(int i = 0; i < pLen; i++){
// 字符与数组索引映射
++count[s.charAt(i) - 'a'];
--count[p.charAt(i) - 'a'];
}
// 记录当前窗口与目标字符种类不同的个数,检验是否找到字母异位体
// 相同字符不同个数不重复计入
int differ = 0;
for (int j = 0; j < 26; ++j) {
if (count[j] != 0) {
++differ;
}
}
if(differ == 0){
ans.add(0);
}
// 移动滑动窗口
// 此时,i 代表滑动窗口的左边界以及 s 中剩下的字符,即滑动窗口需要移动的次数
// 检验是否异位体,是通过判断 differ 是否为 0
for(int i = 0; i < sLen - pLen; i++){
// 移动左边界
// 左边界的变动会导致滑动窗口的字符发生变化
// 需要判断会不会导致 differ 变化,即窗口找到异位体
if (count[s.charAt(i) - 'a'] == 1) {
--differ;
} else if (count[s.charAt(i) - 'a'] == 0) {
++differ;
}
--count[s.charAt(i) - 'a']; // 真正移动
// 移动右边界
// 右边界的变动也会导致滑动窗口的字符发生变化
// 也需要判断会不会导致 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']; // 真正移动
// 判断
if (differ == 0) {
ans.add(i + 1);
}
}
return ans;
}
}