给定一个字符串 S 和一个字符串 T,请在 S 中找出包含 T 所有字母的最小子串。
示例:
输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:
""
。解题思路
对于这个问题我们首先就会相同通过对撞指针
的方法来解。这里我们有一个比较难解决的问题就是包含 T 所有字母的最小子串怎么表示?首先想到的做法时对于输入的s
子串sW
和输入的t
通过collection.Counter
分别统计字母个数,得到两个字母个数字典sW_count
和tW_count
,然后遍历tW_count
种的每个元素,判断它是不是在sW_count
中,并且其元素个数不大于sW_count
中的。于是我们可以写出这样的代码
def _minWindow(self, sW, tW):
sW_count, tW_count = Counter(sW), Counter(tW)
for i, val in tW_count.items():
if i in sW_count and val <= sW_count[i]:
continue
return False
return True
但是这种做法我们在解决问题的过程中,出现了超时。很容易理解,对于一个很长的字符串来说,这个字符串包含了很多的子串,而我们对每个子串进行统计,这里面实际上有大量的冗余。所以我们可以通过动态记录一个dict
来做为我们的子串字典。
接着,我们建立两个指针l
和r
分别指向窗口的左右,初始值赋为0
和t_len
。
A D O B E C O D E B A N C
↑ ↑
l r
然后我们建立一个dict:sW_count
,存储[l,r]
这个区间内的元素个数。我们同时建立一个dict:tW_count
,记录t
中的元素个数。当[l,r]
区间内的元素个数不小于t
中的元素个数时,我们要缩小这个窗口,也就是l--
,而区间内的元素小于t
中元素的时候,我们又要将这个窗口放宽,也就是r++
。
from collections import Counter
class Solution:
def minWindow(self, s, t):
"""
:type s: str
:type t: str
:rtype: str
"""
if not s or not t:
return ''
s_len, t_len = len(s), len(t)
if t_len > s_len:
return ''
l, r, minLength, minL, minR = 0, t_len, s_len+1, 0, 0
tW_count = Counter(t)
sW_count = Counter(s[l:r])
while l < s_len:
if self._minWindow(sW_count, tW_count):
if minLength > r - l:
minLength = r - l
minL, minR = l, r
if sW_count[s[l]] == 1:
sW_count.pop(s[l])
else:
sW_count[s[l]] -= 1
l += 1
else:
if r == s_len:
break
sW_count[s[r]] = sW_count.get(s[r], 0) + 1
r +=1
return s[minL:minR]
def _minWindow(self, sW, tW):
for i, val in tW.items():
if i in sW and val <= sW[i]:
continue
return False
return True
上面这种做法思路很清晰,但是不够优雅。其实这个问题和Leetcode 3:无重复字符的最长子串(最详细解决方案!!!) 是一类问题,都可以归纳为子串问题,这类问题最优美的解法就是Leetcode 3:无重复字符的最长子串(最详细解决方案!!!) 中的解法2,通过借助hash
表来处理。
以题目中的例如为例,我们先建立滑动窗口,滑动窗口的边界是l
和r
,我们的l
初始化为0
,而r
对应当前s
中遍历到元素的位置。
A D O B E C O D E B A N C
↑ ↑
l r
mem:A0 B0 C0 D-1 E-1 O-1
t_len:0
我们首先建立一个可以容纳ASCII的字典mem
,然后记录t
中元素出现的次数。接着我们遍历s
中的元素c
,我们将字典mem
中的c
元素减一,如果s
中的元素在t
中出现过,那么我们记录此时的t_len = len(t)-1
。当t_len==0
时,说明此时t
中的元素在s
中都出现了,我们此时要进入下一步判断。
我们要判断mem
中s[l]
是不是小于0
,如果是的话,说明t
中不应该包含s[l]
,所以我们需要将它剔除出去(也就是mem[s[l++]]++
)。否则的话,我们记录此时的l
和r
。接着我们需要继续找符合条件的位置,我们将l++
并且t_len++
,将s[l]
再加入到mem
中去,这样我们就将窗口向右滑动一步
A D O B E C O D E B A N C
↑ ↑
l r
mem:A1 B0 C0 D-1 E-1 O-1
t_len:1
我们基于此就可以写出下面的代码
from collections import Counter
class Solution:
def minWindow(self, s, t):
mem = Counter(t)
t_len = len(t)
minL, minR = 0, float('inf')
l = 0
for r, c in enumerate(s):
if mem[c] > 0:
t_len -= 1
mem[c] -= 1
if t_len == 0:
while mem[s[l]] < 0:
mem[s[l]] += 1
l += 1
if r - l < minR - minL:
minL, minR = l, r
mem[s[l]] += 1
t_len += 1
l += 1
return '' if minR == float('inf') else s[minL:minR+1]
这种写法比上面要简洁,速度要快(我们没有对字典进行插入和删除操作)。
该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!