1. 题目
题目
https://leetcode.cn/problems/minimum-window-substring/
这道题目是要求给定两个字符串s和t,找出s中包含t中所有字符的最小子串,如果不存在这样的子串,就返回空字符串。
例如,输入s = "ADOBECODEBANC", t = "ABC",输出"BANC"。¹²
这道题目的难度是Hard,需要用到滑动窗口和哈希表的技巧
考查点
这道题考查的是滑动窗口的技巧,以及如何用哈希表来记录和判断字符的出现情况。
- 滑动窗口是一种常用的处理字符串或数组的方法,可以有效地减少重复的计算和遍历。
- 哈希表是一种常用的数据结构,可以快速地查找和更新键值对。
-
2. 解法
解题思路
这个问题的关键是找到一个包含 t 中所有字符的最小子串,也就是说,这个子串中每个字符的出现次数要大于等于 t 中对应字符的出现次数。为了做到这一点,我们可以用一个哈希表来记录 t 中每个字符的出现次数,然后用两个指针 left 和 right 来维护一个滑动窗口,表示当前考虑的子串。初始时,left 和 right 都指向 s 的第一个字符。
我们从右边开始扩展窗口,每次移动 right 指针,如果 right 指向的字符在 t 中出现过,就把哈希表中对应的值减一。如果减一后的值等于零,说明我们已经找到了 t 中的一个字符,并且出现次数刚好满足要求。为了记录这种情况,我们用一个变量 count 来表示 t 中不同字符的个数,每次找到一个字符就把 count 减一。当 count 等于零时,说明我们找到了一个包含 t 中所有字符的子串。
接下来,我们要尝试缩小窗口,从左边开始移动 left 指针,如果 left 指向的字符在 t 中出现过,就把哈希表中对应的值加一。如果加一后的值大于零,说明我们失去了 t 中的一个字符,并且出现次数不再满足要求。为了记录这种情况,我们把 count 加一。当 count 大于零时,说明我们需要继续扩展窗口。
在这个过程中,我们每次找到一个有效的窗口时,就更新最小长度和起始位置。最后,我们返回最小长度对应的子串或者空字符串。
好的,我可以用以下的步骤来总结我的思路:
- 创建一个哈希表 map,记录 t 中每个字符的出现次数。
- 初始化两个指针 left 和 right,都指向 s 的第一个字符,表示当前的窗口。
- 初始化一个变量 count,表示 t 中不同字符的个数。
- 循环以下步骤,直到 right 到达 s 的末尾:
- 如果 right 指向的字符在 map 中存在,就把 map 中对应的值减一。如果减一后的值等于零,就把 count 减一。
- 移动 right 指针,扩展窗口。
- 如果 count 等于零,说明当前的窗口包含了 t 中所有字符,进入以下步骤:
- 如果当前的窗口长度小于之前记录的最小长度,就更新最小长度和起始位置。
- 如果 left 指向的字符在 map 中存在,就把 map 中对应的值加一。如果加一后的值大于零,就把 count 加一。
- 移动 left 指针,缩小窗口。
- 返回最小长度对应的子串或者空字符串。
具体实现
class Solution { public String minWindow(String s, String t) { // Use a map to store the frequency of each character in t Map<Character, Integer> map = new HashMap<>(); for (char c : t.toCharArray()) { map.put(c, map.getOrDefault(c, 0) + 1); } // Use two pointers to maintain a sliding window int left = 0; int right = 0; int count = map.size(); // The number of distinct characters in t int minLen = Integer.MAX_VALUE; // The minimum length of the window int start = 0; // The start index of the window while (right < s.length()) { char c = s.charAt(right); // If c is in t, decrease its frequency by one if (map.containsKey(c)) { map.put(c, map.get(c) - 1); // If c's frequency becomes zero, decrease the count by one if (map.get(c) == 0) { count--; } } // Move the right pointer forward right++; // When the count is zero, we have found a valid window while (count == 0) { // Update the minimum length and start index if needed if (right - left < minLen) { minLen = right - left; start = left; } char d = s.charAt(left); // If d is in t, increase its frequency by one if (map.containsKey(d)) { map.put(d, map.get(d) + 1); // If d's frequency becomes positive, increase the count by one if (map.get(d) > 0) { count++; } } // Move the left pointer forward left++; } } // Return the substring or empty string return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen); } }
3. 总结