算法题10:最小覆盖子串
题目描述:
给你一个字符串s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。注意:
- 对于
t中重复字符,我们寻找的子字符串中该字符数量必须不少于t中该字符数量。 - 如果
s中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC" 输出:"BANC" 解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。示例 2:
输入:s = "a", t = "a" 输出:"a" 解释:整个字符串 s 是最小覆盖子串。示例 3:
输入: s = "a", t = "aa" 输出: "" 解释: t 中两个字符 'a' 均应包含在 s 的子串中, 因此没有符合条件的子字符串,返回空字符串。
思路:
开始时想到滑动窗口,但是实现方式不对
class Solution: def minWindow(self, s: str, t: str) -> str: res = "" s_n = len(s) t_n = len(t) if s == t: return s for i in range(s_n): right = i+t_n while right < s_n + 1: s1 = s[i:right] if self.checkIn(s1, t): res = s[i:right] break right += 1 return res def checkIn(self, s, t): flag = True for i in t: if i not in s: flag = False return flag return flag
结果没通过,看灵茶山艾府的解题,相对官方解题更简洁易懂一些:
涵盖:
这里用到一个概念“涵盖”,示例中s的字串BANC中没字母出现次数都大于等于t=ABC中每个字母的出现次数,这就叫涵盖。
滑动窗口怎么滑
枚举s子串的右端点right(子串最后一个字母的下标),如果子串涵盖t,就不断移动左端点left知道不涵盖位置。在移动过程中更新最短子串的左右断点。
具体步骤:
1、初始化ansLeft = -1, ansRight = m, 用来记录最短子串的左右端点,其中m是s的长度。看起来就是左右端点都初始化为最右侧一个字母的位置。
2、用一个哈希表(或数组)cntT统计t中每个字母的出现次数。
3、初始化left = 0, 以及一个空哈希表(或数组)cntS,用来统计s子串中每个字母的出现次数,用来判断是否涵盖。
4、遍历s,设当前枚举的子串右端点为right,把s[right]的出现次数加一。窗口右端点向右移动。
5、遍历cntS中每个字母及其出现次数,如果出现次数都大于等于cntT中的字母出现次数,满足涵盖条件则判断:
a.如果right - left < ansRight - ansLeft,说明找到了更短的子串,更新ansLeft = left, ansRight = right。
b.把s[left]的出现次数减一。窗口左侧端点向右移动后对应的次数减一。
c.左端点右移,即left加一。窗口左侧端点向右移动。
d.重复上述三步,知道cntS有字母的出现次数小于cntT中该字母出现的次数位置。即涵盖条件不满足。
6、最后,如果ansLeft < 0,说明没有找到符合要求的子串,返回空字符串,否则返回下标ansLeft到下标ansRight之间的子串。
python
class Solution: def minWindow(self, s: str, t: str) -> str: ans_left, ans_right = -1, len(s) cnt_s = Counter() # s子串字母的出现次数 cnt_t = Counter(t) # t中字母的出现次数 left = 0 for right, c in enumerate(s): # 移动子串右端点 cnt_s[c] += 1 # 右端点字母移入子串 while cnt_s >= cnt_t: # 涵盖,这里体现出python的简单 整个Counter()对象可以对比 if right - left < ans_right - ans_left: # 找到更短子串 ans_left, ans_right = left, right # 记录此时的左右端点 cnt_s[s[left]] -= 1 # 左端点字母移出子串 left += 1 # 左端点右移 return "" if ans_left < 0 else s[ans_left : ans_right + 1]
结果:

java:
class Solution { public String minWindow(String S, String t) { char[] s = S.toCharArray(); int m = s.length; int ansLeft = -1; int ansRight = m; int [] cntS = new int[128]; // s子串字母的出现次数 int [] cntT = new int[128]; // t中字母的出现次数 for (char c : t.toCharArray()){ cntT[c] ++; } int left = 0; for (int right = 0; right < m; right ++) { // 移动子串右端点 cntS[s[right]]++; // 右端点字母移入窗口(子串) while (isCovered(cntS, cntT)) { // 涵盖 if (right - left < ansRight - ansLeft) { //找到更短的子串 ansLeft = left; // 记录次数的左右端点 ansRight = right; } cntS[s[left]] --; // 左端点字母移出子串(窗口) left++; } } return ansLeft < 0 ? "" : S.substring(ansLeft, ansRight + 1); } private boolean isCovered(int[] cntS, int[] cntT) { for (int i = 'A'; i <= 'Z'; i++) { if (cntS[i] < cntT[i]) { return false; } } for (int i = 'a'; i <= 'z'; i++) { if (cntS[i] < cntT[i]) { return false; } } return true; } }
结果:


浙公网安备 33010602011771号