Skip to main content

76.最小覆盖子串

标签: two-pointers, hash-table, string, queue

难度: Hard

通过率: 44.21%

原题链接: https://leetcode.com/problems/minimum-window-substring/description/

题目描述

给定两个字符串 st,分别长度为 mn,返回 s 的最小子串,使得 t 中的每个字符(包括重复字符)都包含在内。如果没有这样的子串,返回空字符串 ""。测试用例保证答案是唯一的。

解题思路

这个问题可以通过滑动窗口算法来解决:

  1. 初始化与计数:使用两个哈希表,一个用来统计 t 中字符的频次(target_map),另一个是当前窗口中字符的频次(window_map)。同时,have 变量用于记录窗口中满足条件的字符个数,needt 中不同字符的数量。

  2. 滑动窗口:使用双指针技巧维护滑动窗口,左指针 l 和右指针 r。初始时两个指针都指向起始位置。右指针 r 向右扩展窗口,l 为窗口的左边界。

  3. 扩展和收缩窗口

    • 移动右指针,包含当前字符到 window_map 中。
    • 如果 window_map 中某个字符的数量与 target_map 中该字符的数量相同,增加 have
    • have 等于 need,说明当前窗口已经包含了 t 中所有的字符。
    • 在此情况下,尝试收缩窗口。同时,在合适的情况下记录最小窗口。
    • 否则,继续扩展窗口。
  4. 更新最小窗口:每次窗口满足条件时,记录窗口大小,并更新最小窗口起始和结束位置。

  5. 返回结果:最后利用记录的最小起始和结束索引返回结果子串。

代码实现

from collections import Counter, defaultdict

def minWindow(s: str, t: str) -> str:
if not s or not t:
return ""

# Counter for characters in t
target_map = Counter(t)
# Map for the sliding window
window_map = defaultdict(int)

# Two pointers, starting from both ends
l, r = 0, 0
have, need = 0, len(target_map)
res, res_len = [-1, -1], float('inf')

# Expand the right pointer
while r < len(s):
# Add character from right to window map
char = s[r]
window_map[char] += 1

# If the frequency of current character in window equals frequency in target, update 'have'
if char in target_map and window_map[char] == target_map[char]:
have += 1

# When the current window satisfies the condition
while have == need:
# Update the result if current window is smaller
if (r - l + 1) < res_len:
res = [l, r]
res_len = r - l + 1

# Pop from left of the window
pop_char = s[l]
window_map[pop_char] -= 1
if pop_char in target_map and window_map[pop_char] < target_map[pop_char]:
have -= 1
l += 1

# Move right pointer
r += 1

l, r = res
return s[l:r+1] if res_len != float('inf') else ""

复杂度分析

  • 时间复杂度: O(m+n)O(m + n),因为每个字符在最坏情况下只会被处理两次(一次被添加到窗口中,一次被移出窗口)。
  • 空间复杂度: O(n+Σ)O(n + |\Sigma|),其中 Σ|\Sigma| 是字符集的大小,为频次表的大小。