这是力扣第 567 题「字符串的排列」,难度中等:

注意哦,输入的 s1 是可以包含重复字符的,所以这个题难度不小。
这种题目,是明显的滑动窗口算法,相当给你一个 S 和一个 T,请问你 S 中是否存在一个子串,包含 T 中所有字符且不包含其他字符?
根据 1. 最小覆盖子串 给出的框架,写出下面算法:
// 注意:java 代码由 chatGPT🤖 根据我的 cpp 代码翻译,旨在帮助不同背景的读者理解算法逻辑。
// 本代码不保证正确性,仅供参考。如有疑惑,可以参照我写的 cpp 代码对比查看。
public boolean checkInclusion(String t, String s) {
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
char c = t.charAt(i);
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0;
int valid = 0;
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 >= t.length()) {
// 在这里判断是否找到了合法的子串
if (valid == need.size())
return true;
char d = s.charAt(left);
left++;
if (need.containsKey(d)) {
if (window.get(d).equals(need.get(d)))
valid--;
window.put(d, window.getOrDefault(d, 0) - 1);
}
}
}
// 未找到符合条件的子串
return false;
}
对于这道题的解法代码,基本上和最小覆盖子串一模一样,只需要改变几个地方:
1、本题移动 left 缩小窗口的时机是窗口大小大于 t.size() 时,因为排列嘛,显然长度应该是一样的。
2、当发现 valid == need.size() 时,就说明窗口中就是一个合法的排列,所以立即返回 true。
至于如何处理窗口的扩大和缩小,和最小覆盖子串完全相同。
Note
由于这道题中
[left, right)其实维护的是一个定长的窗口,窗口大小为t.size()。因为定长窗口每次向前滑动时只会移出一个字符,所以可以把内层的 while 改成 if,效果是一样的。
作者的可视算法动画: 我写了首诗,把滑动窗口算法变成了默写题 | labuladong 的算法笔记