💡 【LeetCode Hot100】找到字符串中所有字母异位词 ,滑动窗口+数组,Java实现!图解+代码,小白也能秒懂!
✏️ 题目链接:找到字符串中所有字母异位词
📌 题目描述
给定两个字符串 s
和 p
,找到 s
中所有 p
的 字母异位词 的子串,返回这些子串的起始索引。
字母异位词:由相同字母重排列形成的字符串(包括相同的字符串)。
示例:
输入:s = "cbaebabacd", p = "abc"
输出:[0, 6]
解释:
起始索引 0 的子串 "cba" 是 "abc" 的字母异位词。
起始索引 6 的子串 "bac" 是 "abc" 的字母异位词。
🧠 解题思路
核心问题
如何在 s
中高效找到所有满足条件的子串?
方法:滑动窗口 + 数组统计法
- 初始化:
- 使用两个长度为 26 的数组
pCnt
和sCnt
,分别统计p
和当前窗口中每个字符的出现次数。
- 使用两个长度为 26 的数组
- 统计字符频率:
- 遍历
p
和s
的前m
个字符,初始化pCnt
和sCnt
。
- 遍历
- 滑动窗口:
- 从
m
开始遍历s
,每次移动窗口:- 移除左边界字符,更新
sCnt
。 - 添加右边界字符,更新
sCnt
。 - 如果
sCnt
和pCnt
相等,说明当前窗口是p
的字母异位词,记录起始索引。
- 移除左边界字符,更新
- 从
- 返回结果:所有满足条件的子串起始索引。
🚀 代码实现
public static List<Integer> findAnagrams2(String s, String p) {
int n = s.length(), m = p.length();
List<Integer> res = new ArrayList<>();
// 如果 s 的长度小于 p 的长度,直接返回空列表
if (n < m) return res;
// 初始化统计数组
int[] pCnt = new int[26]; // 统计 p 中每个字符的频率
int[] sCnt = new int[26]; // 统计当前窗口中每个字符的频率
// 遍历 p 和 s 的前 m 个字符,初始化 pCnt 和 sCnt
for (int i = 0; i < m; i++) {
pCnt[p.charAt(i) - 'a']++; // 统计 p 中字符的频率
sCnt[s.charAt(i) - 'a']++; // 统计 s 中前 m 个字符的频率
}
// 如果初始窗口满足条件,记录起始索引 0
if (Arrays.equals(sCnt, pCnt)) {
res.add(0);
}
// 滑动窗口遍历
for (int i = m; i < n; i++) {
// 移除左边界字符
sCnt[s.charAt(i - m) - 'a']--;
// 添加右边界字符
sCnt[s.charAt(i) - 'a']++;
// 如果当前窗口满足条件,记录起始索引
if (Arrays.equals(sCnt, pCnt)) {
res.add(i - m + 1);
}
}
return res;
}
🎨 图解滑动窗口
示例:
s = "cbaebabacd"
p = "abc"
步骤1:初始化
pCnt = [1, 1, 1, 0, 0, ...]
(a:1
,b:1
,c:1
)sCnt = [1, 1, 1, 0, 0, ...]
(c:1
,b:1
,a:1
)res = [0]
(初始窗口满足条件)
步骤2:滑动窗口
- 窗口右移:
- 移除
c
,添加e
→sCnt = [1, 1, 0, 0, 1, ...]
- 移除
b
,添加b
→sCnt = [1, 1, 0, 0, 1, ...]
- 移除
a
,添加a
→sCnt = [1, 1, 0, 0, 1, ...]
- 移除
e
,添加b
→sCnt = [1, 2, 0, 0, 0, ...]
- 移除
b
,添加a
→sCnt = [2, 1, 0, 0, 0, ...]
- 移除
a
,添加c
→sCnt = [1, 1, 1, 0, 0, ...]
(满足条件)- 添加索引
6
到res
。
- 添加索引
- 移除
最终结果:
res = [0, 6]
💡 复杂度分析
- 时间复杂度:O(n)
- 只需遍历字符串一次。
- 空间复杂度:O(1)
- 使用固定大小的数组。
🌟 总结
- 核心思想:通过滑动窗口动态维护字符频率,利用数组快速比较窗口是否满足条件。
- 适用场景:字符串匹配、子串问题。