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

image.png

注意哦,输入的 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 的算法笔记