如何想到这个解法
问题的特点:
- 首先,认识到这是一个关于子串的问题,而且需要考虑子串的最小长度。这提示我们可能需要使用一种方式来逐步探索不同的子串。
- 滑动窗口的适用性:滑动窗口是处理子串问题的常用技巧,特别是当我们需要找到满足特定条件的最短或最长子串时。在这个问题中,我们需要找到包含所有特定字符的最短子串,这正是滑动窗口擅长的。
- 动态调整窗口大小:认识到我们可以动态地调整窗口的大小来探索不同的子串。通过扩展和收缩窗口的边界,我们可以有效地覆盖所有可能的子串,同时保持对窗口内字符的跟踪。
- 字符计数:为了判断窗口内的子串是否包含了 t 中的所有字符,我们可以使用字符计数。通过比较 s 的子串和 t 中字符的计数,我们可以知道当前窗口是否满足条件。
- 优化性能:理解到在移动窗口时,我们不需要每次都从头开始计算。我们可以利用之前的计算结果,每次只对进入和离开窗口的字符进行计数更新。这样,算法的效率更高。
解题思路的构思
- 初始化计数器和窗口指针:首先,计算字符串 t 中每个字符的出现次数,并初始化两个指针(l 和 r)来表示窗口的左右边界。
- 移动右边界以扩展窗口:逐步移动右边界,每次移动都更新窗口内的字符计数。每当窗口包含所有 t 中的字符时,我们就找到了一个潜在的答案。
- 收缩左边界以缩小窗口:一旦窗口满足条件,尝试移动左边界以缩小窗口大小。这一步是为了找到更短的满足条件的子串。
- 更新最短覆盖子串:在每个满足条件的窗口中,如果当前窗口的长度小于之前找到的最短子串,更新最短子串。
- 继续直到结束:重复以上步骤,直到右边界到达字符串 s 的末尾。
class Solution {public static String minWindow(String s, String t) {// 检查输入字符串是否有效if (s == null || t == null || s.length() < t.length()) {return "";}// tCount 用于存储 t 中每个字符出现的次数int[] tCount = new int[100];for (int i = 0; i < t.length(); i++) {tCount[t.charAt(i) - 'A']++;}// sCount 用于存储当前窗口中每个字符出现的次数int[] sCount = new int[100];// 初始化最小窗口的起始位置和长度int start = 0, minLen = Integer.MAX_VALUE;// required 用于跟踪窗口中还需要多少个 t 中的字符int required = t.length();// 初始化左右指针int l = 0, r = 0;// 遍历 swhile (r < s.length()) {// 当前字符在 t 中if (tCount[s.charAt(r) - 'A'] > 0) {// 更新窗口内字符计数sCount[s.charAt(r) - 'A']++;// 如果窗口中的字符数量满足 t 中的需求,则减少 requiredif (sCount[s.charAt(r) - 'A'] <= tCount[s.charAt(r) - 'A']) {required--;}}// 当 required 为 0 时,窗口已包含 t 的所有字符while (required == 0) {// 检查当前窗口是否是最小窗口if (r - l + 1 < minLen) {minLen = r - l + 1;start = l;}// 尝试移动左边界以缩小窗口if (tCount[s.charAt(l) - 'A'] > 0) {sCount[s.charAt(l) - 'A']--;// 如果移动左边界后窗口不再满足条件,增加 requiredif (sCount[s.charAt(l) - 'A'] < tCount[s.charAt(l) - 'A']) {required++;}}l++;}r++;}// 返回最小覆盖子串,如果没有找到则返回空字符串return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);}
}