【双指针】力扣76:最小覆盖子串(代码错误)
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
该问题要求返回字符串 s 中包含字符串 t 的全部字符的最小窗口。称包含 t 的全部字母的窗口为「可行」窗口。
在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的 r 指针,和一个用于「收缩」窗口的 l 指针。两个指针 l 和 r 都是从最左端向最右端移动,且 l 的位置一定在 r 的左边或重合。
在任意时刻,只有一个指针运动,而另一个保持静止。在字符串 s 上滑动窗口,通过移动 r 指针不断扩张窗口(窗口不包含完整的 t 则 r 右移)。当窗口包含字符串 t 全部所需的字符后,如果能收缩,就通过移动 l 指针收缩窗口(窗口包含完整的 t 则 l 右移),直到得到最小窗口。
步骤:
- 不断增加 r 使滑动窗口增大,直到窗口包含了 t 的所有元素;
- 不断增加 l 使滑动窗口缩小,因为是要求最小字串,所以将不必要的元素排除在外,使长度减小,直到碰到一个必须包含的元素,这个时候不能再扔了,再扔就不满足条件了,记录此时滑动窗口的长度
r - j + 1
,并保存最小值; - 让 l 再增加一个位置,这个时候滑动窗口肯定不满足条件了,那么继续从步骤一开始执行,寻找新的满足条件的滑动窗口,如此反复,直到 r 超出了字符串 s 范围。
问题:如何判断滑动窗口包含了T的所有元素?
我们用一个字典(哈希表)need 表示当前滑动窗口中需要的各元素的数量,一开始滑动窗口为空,用 t 中各元素来初始化 need,当滑动窗口扩展或者收缩的时候,去维护need,例如当滑动窗口包含某个元素,就让 need 中这个元素的数量减 1,代表所需元素减少了1个;当滑动窗口移除某个元素,就让 need 中这个元素的数量加1。
记住一点:need 始终记录着当前滑动窗口下还需要的元素数量,在改变 l和r 时,需同步维护 need。
值得注意的是,只要某个元素包含在滑动窗口中,就会在 need 中存储这个元素的数量,如果某个元素存储的是负数代表这个元素是多余的。比如当 need 等于 {'A': -2, 'C': 1} 时,表示当前滑动窗口中,有2个A是多余的,同时还需要1个C。这么做的目的就是在步骤2中排除不必要的元素,数量为负的就是不必要的元素,而数量为0表示刚刚好。
回到问题中来,那么如何判断滑动窗口包含了 t 的所有元素?结论就是当 need 中所有元素的数量都 <= 0 时,表示当前滑动窗口不再需要任何元素。
问题:是否可以优化?
如果每次判断滑动窗口是否包含了 t 的所有元素,都去遍历 need 看是否所有元素数量都小于等于0,这个会耗费O(k)的时间复杂度,k 代表字典长度,最坏情况下,k 可能等于len(s)。
其实这个是可以避免的,可以维护一个额外的变量 needCount 记录升序所需元素的总数量,当我们碰到一个所需元素 char,不仅 need[char] 的数量减少 1,同时 needCount 也要减少1,这样我们通过 needCount 就可以知道是否满足条件,而无需遍历字典了。
前面提到,need 记录了遍历到的所有元素,而只有 need[char] > 0 时,代表 char 就是所需元素。
时间复杂度是 O(n)。两个指针都严格递增,最多移动 n 次
空间复杂度为 O(k)。k 为 s 和 t 的字符数和。
另一种思路:
需要两个哈希表:hs 哈希表维护的是 s 字符串中滑动窗口中各个字符出现多少次,ht 哈希表维护的是 t 字符串各个字符出现多少次。如果 hs 哈希表中包含 ht 哈希表中的所有字符,并且对应的个数都不小于 ht 哈希表中各个字符的个数,那么说明当前的窗口是可行的,可行中的长度最短的滑动窗口就是答案。
过程:
- 遍历 t 字符串,用 ht 哈希表记录 t 字符串各个字符出现的次数。
- 定义两个指针 l 和 r ,l 指针用于收缩窗口,r指针用于延伸窗口,则区间[l, r]表示当前滑动窗口。首先让 l 和 r 指针都指向字符串 s 开头,然后枚举整个字符串 s ,枚举过程中,不断增加 r 使滑动窗口增大,相当于向右扩展滑动窗口。
- 每次向右扩展滑动窗口一步,将 s[r] 加入滑动窗口中,而新加入了s[r]相当于滑动窗口维护的字符数加一,即 hs[s[r]]++ 。
- 对于新加入的字符 s[r],如果 hs[s[r]] <= ht[s[r]],说明当前新加入的字符s[r]是必需的,且还未到达字符串 t 所要求的数量。还需要事先定义一个 cnt 变量,cnt 维护的是 s 字符串[l, r]]区间中满足 t 字符串的元素的个数,记录相对应字符的总数。新加入的字符s[r]必需,则 cnt++。
- 向右扩展滑动窗口的同时也不能忘记收缩滑动窗口。因此当 hs[s[l]] > ht[s[l]时,说明 hs 哈希表中 s[l] 的数量多于 ht 哈希表中 s[l] 的数量,此时我们就需要向右收缩滑动窗口,l++ 并使 hs[s[l]]--,即 hs[s[l ++]] --。
- 当 cnt == len(t) 时,说明此时滑动窗口包含符串 t 的全部字符。我们重复上述过程找到最小窗口即为答案。
from collections import defaultdict
class Solution:
def minWindow(self, s: str, t: str) -> str:
'''
如果hs哈希表中包含ht哈希表中的所有字符,并且对应的个数都不小于ht哈希表中各个字符的个数,那么说明当前的窗口是可行的,可行中的长度最短的滑动窗口就是答案。
'''
if len(s)<len(t):
return ""
hs, ht = defaultdict(int), defaultdict(int)#初始化新加入key的value为0
for char in t:
ht[char] += 1
res = ""
left, right = 0, 0 #滑动窗口
cnt = 0 #当前窗口中满足ht的字符个数
while right<len(s):
hs[s[right]] += 1
if hs[s[right]] <= ht[s[right]]: #必须加入的元素
cnt += 1 #遇到了一个新的字符先加进了hs,所以相等的情况cnt也+1
while left<=right and hs[s[left]] > ht[s[left]]:#窗口内元素都符合,开始压缩窗口
hs[s[left]] -= 1
left += 1
if cnt == len(t):
if not res or right-left+1<len(res): #res为空或者遇到了更短的长度
res = s[left:right+1]
right += 1
return res
作者:lin-shen-shi-jian-lu-k
链接:https://leetcode-cn.com/problems/minimum-window-substring/solution/leetcode-76-zui-xiao-fu-gai-zi-chuan-cja-lmqz/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端