使用哈希表实现LeetCode 438(找到字符串中所有字母异位词)的滑动窗口算法,适用于字符串包含任意字符(不仅限于小写字母)的场景。核心思路是通过哈希表记录字符频率,结合固定大小的滑动窗口判断子串是否为异位词。
实现代码
import java.util.*;
public class FindAnagramsWithHash {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> result = new ArrayList<>();
int sLen = s.length();
int pLen = p.length();
// 边界条件:若s长度小于p,直接返回空列表
if (sLen < pLen) {
return result;
}
// 哈希表存储p中每个字符的频率
Map<Character, Integer> pFreq = new HashMap<>();
// 哈希表存储当前滑动窗口中每个字符的频率
Map<Character, Integer> windowFreq = new HashMap<>();
// 初始化p的频率表
for (char c : p.toCharArray()) {
pFreq.put(c, pFreq.getOrDefault(c, 0) + 1);
}
// 初始化滑动窗口的初始状态(前pLen个字符)
for (int i = 0; i < pLen; i++) {
char c = s.charAt(i);
windowFreq.put(c, windowFreq.getOrDefault(c, 0) + 1);
}
// 检查初始窗口是否为p的异位词
if (isFreqEqual(pFreq, windowFreq)) {
result.add(0);
}
// 滑动窗口:从pLen位置开始向右移动
for (int right = pLen; right < sLen; right++) {
// 1. 加入右侧新字符(扩展窗口右边界)
char newChar = s.charAt(right);
windowFreq.put(newChar, windowFreq.getOrDefault(newChar, 0) + 1);
// 2. 移除左侧溢出字符(收缩窗口左边界,保持窗口大小为pLen)
char leftChar = s.charAt(right - pLen);
int count = windowFreq.get(leftChar);
if (count == 1) {
windowFreq.remove(leftChar); // 频率为1时,移除后频率为0,直接删除键
} else {
windowFreq.put(leftChar, count - 1); // 频率减1
}
// 3. 检查当前窗口是否与p的频率匹配
if (isFreqEqual(pFreq, windowFreq)) {
// 当前窗口的起始索引 = right - pLen + 1
result.add(right - pLen + 1);
}
}
return result;
}
// 辅助函数:判断两个频率哈希表是否完全相等
private boolean isFreqEqual(Map<Character, Integer> map1, Map<Character, Integer> map2) {
// 若键的数量不同,直接不匹配
if (map1.size() != map2.size()) {
return false;
}
// 逐个检查键对应的频率是否相同
for (char c : map1.keySet()) {
// 若map2中没有该键,或频率不同,则不匹配
if (!map2.containsKey(c) || !map1.get(c).equals(map2.get(c))) {
return false;
}
}
return true;
}
}
关键思路解析
- 哈希表的作用:
-
pFreq 存储字符串 p 中每个字符的出现次数(频率)。 -
windowFreq 存储当前滑动窗口(长度为 pLen)中每个字符的频率。
- 滑动窗口逻辑:
- 窗口大小固定为
pLen(异位词长度必与 p 相同)。 - 右指针
right 从 pLen 开始移动,每次移动时:
- 加入右侧新字符
s[right],更新 windowFreq。 - 移除左侧溢出字符
s[right - pLen](保证窗口长度不变),更新 windowFreq(若频率为0则直接删除键,避免干扰比较)。
- 频率匹配判断:
- 自定义
isFreqEqual 函数,通过对比两个哈希表的键数量和对应频率,判断是否为异位词。
时间复杂度与空间复杂度
- 时间复杂度:O(sLen * k),其中
sLen 是 s 的长度,k 是 p 中不同字符的数量(哈希表键的数量)。每个字符最多被加入和移除窗口各一次,每次频率比较需遍历 k 个键。 - 空间复杂度:O(k),两个哈希表最多存储
k 个键(k 为 p 中不同字符的数量)。
适用场景
当字符串 s 和 p 包含 任意字符(如大写字母、数字、符号等)时,无法用固定大小的数组统计频率,此时哈希表实现更通用。例如:
- 输入
s = "aBcAbC", p = "abc"(包含大写字母) - 输入
s = "12321", p = "321"(包含数字)
这种实现通过哈希表灵活记录字符频率,结合滑动窗口高效匹配异位词,兼顾了通用性和效率。