Given two strings s
and t
of lengths m
and n
respectively, return the minimum window substring of s
such that every character in t
(including duplicates) is included in the window. If there is no such substring, return the empty string ""
.
The testcases will be generated such that the answer is unique.
A substring is a contiguous sequence of characters within the string.
Example 1:
Input: s = "ADOBECODEBANC", t = "ABC" Output: "BANC" Explanation: The minimum window substring "BANC" includes 'A', 'B', and 'C' from string t.
Example 2:
Input: s = "a", t = "a" Output: "a" Explanation: The entire string s is the minimum window.
Example 3:
Input: s = "a", t = "aa" Output: "" Explanation: Both 'a's from t must be included in the window. Since the largest window of s only has one 'a', return empty string.
Constraints:
m == s.length
n == t.length
1 <= m, n <= 105
s
andt
consist of uppercase and lowercase English letters.
Follow up: Could you find an algorithm that runs in O(m + n)
time?
最小覆盖子串。
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路依然是滑动窗口(sliding window),这个题解是具有普适性的,可以套用到多个LC的题目中。如下是几个细节,
- 创建一个hashmap,记录T中出现的字母及其次数
- 创建一个变量count去记录T的长度
- 创建两个指针left和right,一前一后扫描S,right在前,left在后,去卡S里面的子串,看这个子串里面是否包含T中所有的字符
- 最后跳出循环的条件是right已经扫描完整个S的长度
扫描的一开始,是 right 指针往前走,每遇到一个字符,无论他在不在T中,就都去map中--这个字符;如果这个字符在T中也有,同时也要去count--。当 count == 0 的时候,证明此时的子串已经包含了T中所有的字母了,可以考虑缩减 start 和 end 之间的距离了。缩减的条件是 while (count == 0),去看 end - start 是否能缩短那个最小长度 minLen,同时开始挪动 start 指针;若能缩短,也要记录一个变量 minStart,这样就能记住最短的子串到底从什么位置开始的。挪动 start 指针的时候也要去 map 中补足当前遍历到的字符的出现次数。如果当前字符在 map 中的 value 大于 0 了,count 也需要++,因为这说明有一个字符已经不在子串中了,此时会跳出 while 循环。最后最短子串是 s.substring(minStart, minStart + minLen)。
时间O(n)
空间O(1) - 128位的array几乎可以忽略不计
Java实现
1 class Solution { 2 public String minWindow(String s, String t) { 3 int[] map = new int[128]; 4 for (char c : t.toCharArray()) { 5 map[c]++; 6 } 7 // end指针在右边,start指针在左边 8 int start = 0; 9 int end = 0; 10 // 最短子串的起点 11 int minStart = 0; 12 // 最短子串的长度 13 int minLen = Integer.MAX_VALUE; 14 int counter = t.length(); 15 while (end < s.length()) { 16 char c1 = s.charAt(end); 17 if (map[c1] > 0) { 18 counter--; 19 } 20 map[c1]--; 21 end++; 22 // 如果找到了所有t中的字符,可以试着缩小窗口的距离了 23 while (counter == 0) { 24 // 试图更新最短子串的长度和起点 25 if (minLen > end - start) { 26 minLen = end - start; 27 minStart = start; 28 } 29 char c2 = s.charAt(start); 30 map[c2]++; 31 start++; 32 if (map[c2] > 0) { 33 counter++; 34 } 35 } 36 } 37 return minLen == Integer.MAX_VALUE ? "" : s.substring(minStart, minStart + minLen); 38 } 39 }
JavaScript实现
1 /** 2 * @param {string} s 3 * @param {string} t 4 * @return {string} 5 */ 6 var minWindow = function (s, t) { 7 let ans = ''; 8 // save all the letters in t to a hashmap 9 let map = {}; 10 t.split('').forEach(ch => map[ch] = (map[ch] || 0) + 1); 11 let count = Object.keys(map).length; 12 13 // traverse s 14 let l = 0; 15 let r = -1; 16 while (r < s.length) { 17 if (count === 0) { 18 // l~r contains t 19 if (!ans || r - l + 1 < ans.length) { 20 ans = s.slice(l, r + 1); 21 } 22 // get rid of curr ch and then move l 23 if (map[s[l]] !== undefined) { 24 map[s[l]]++; 25 } 26 if (map[s[l]] > 0) { 27 count++; 28 } 29 l++; 30 } else { 31 // l~r doesn't contain t 32 // move r and add new ch 33 r++; 34 if (map[s[r]] !== undefined) { 35 map[s[r]]--; 36 } 37 if (map[s[r]] === 0) { 38 count--; 39 } 40 } 41 } 42 return ans; 43 };