法算day12

法算day12
lief
[up主专用,视频内嵌代码贴在这]
最小覆盖子串
问题描述
给你一个字符串 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 的子串中,
因此没有符合条件的子字符串,返回空字符串。
提示:
m == s.length
n == t.length
1 <= m, n <= 105
s 和 t 由英文字母组成
算法分析
这是一个典型的滑动窗口问题,需要在字符串s中找到包含字符串t所有字符的最小子串。算法使用了双指针技巧和哈希表来高效解决这个问题。
- 初始化阶段
1
2need_dict = defaultdict(int) # 记录t中每个字符的出现次数
window_dict = defaultdict(int) # 记录当前窗口中每个字符的出现次数
- need_dict:统计目标字符串t中每个字符的出现频率
- window_dict:统计当前滑动窗口中每个字符的出现频率
- 滑动窗口过程
算法通过两个指针left和right维护一个滑动窗口:
- 扩展窗口(右指针移动):
右指针向右移动,将新字符加入窗口
更新窗口字符计数
如果当前字符在t中,并且窗口中该字符的数量等于t中该字符的数量,则增加有效计数valid - 收缩窗口(左指针移动):
当窗口包含t的所有字符时(valid == len(need_dict))
尝试收缩左指针,寻找更小的满足条件的窗口
更新最小窗口记录
如果移出的字符在t中,更新窗口计数和有效计数
- 终止条件
- 右指针遍历完整个字符串s
- 返回找到的最小窗口子串
算法关键点
滑动窗口策略:
右指针扩展窗口,直到包含t的所有字符
左指针收缩窗口,寻找最小满足条件的窗口有效计数机制:
只有当窗口中某个字符的数量恰好等于t中该字符的数量时,才增加valid
这确保了valid准确反映窗口是否包含t的所有字符最小窗口更新:
每次找到满足条件的窗口时,记录并更新最小窗口
通过收缩窗口寻找更小的满足条件的窗口
python版
1 | from collections import defaultdict |
✅ 说明:
- 时间复杂度:O(n),其中n是字符串s的长度
- 空间复杂度:O(m),其中m是字符集的大小
举例说明s = “ADOBECODEBANC”;t = “ABC”
- 初始状态
1
2
3
4
5
6
7
8s: A D O B E C O D E B A N C
t: A B C
need_dict: {'A':1, 'B':1, 'C':1}
window_dict: {}
left = 0, right = 0
valid = 0
min_len = ∞, start = 0 - 步骤1:right=0, char=’A’
1
2
3
4
5
6
7
8
9当前窗口: [A]
操作:
- A在need_dict中
- window_dict['A'] = 1
- window_dict['A'] == need_dict['A'] (1==1) → valid=1
状态:
window_dict: {'A':1}
valid = 1 (A满足要求)
不满足条件(valid=1 < 3),继续扩展 - 步骤2:right=1, char=’D’
1
2
3
4
5
6
7当前窗口: [A D]
操作:
- D不在need_dict中,跳过
状态:
window_dict: {'A':1}
valid = 1
不满足条件,继续扩展 - 步骤3:right=2, char=’O’
1
2
3
4
5
6
7当前窗口: [A D O]
操作:
- O不在need_dict中,跳过
状态:
window_dict: {'A':1}
valid = 1
不满足条件,继续扩展 - 步骤4:right=3, char=’B’
1
2
3
4
5
6
7
8
9当前窗口: [A D O B]
操作:
- B在need_dict中
- window_dict['B'] = 1
- window_dict['B'] == need_dict['B'] (1==1) → valid=2
状态:
window_dict: {'A':1, 'B':1}
valid = 2 (A和B满足要求)
不满足条件(valid=2 < 3),继续扩展 - 步骤5:right=4, char=’E’
1
2
3
4
5
6
7当前窗口: [A D O B E]
操作:
- E不在need_dict中,跳过
状态:
window_dict: {'A':1, 'B':1}
valid = 2
不满足条件,继续扩展 - 步骤6:right=5, char=’C’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23当前窗口: [A D O B E C]
操作:
- C在need_dict中
- window_dict['C'] = 1
- window_dict['C'] == need_dict['C'] (1==1) → valid=3
状态:
window_dict: {'A':1, 'B':1, 'C':1}
valid = 3 (A、B、C都满足要求)
满足条件(valid=3 == 3),开始收缩窗口
// 收缩窗口
当前窗口长度: 6-0=6
更新最小窗口: min_len=6, start=0
移除left=0的字符'A':
- A在need_dict中
- window_dict['A'] == need_dict['A'] (1==1) → valid=2
- window_dict['A'] = 0
状态:
window_dict: {'A':0, 'B':1, 'C':1}
valid = 2
不满足条件,停止收缩 - 步骤7:right=6, char=’O’
1
2
3
4
5
6
7当前窗口: [D O B E C O]
操作:
- O不在need_dict中,跳过
状态:
window_dict: {'A':0, 'B':1, 'C':1}
valid = 2
不满足条件,继续扩展 - 步骤8:right=7, char=’D’
1
2
3
4
5
6
7当前窗口: [D O B E C O D]
操作:
- D不在need_dict中,跳过
状态:
window_dict: {'A':0, 'B':1, 'C':1}
valid = 2
不满足条件,继续扩展 - 步骤9:right=8, char=’E’
1
2
3
4
5
6
7当前窗口: [D O B E C O D E]
操作:
- E不在need_dict中,跳过
状态:
window_dict: {'A':0, 'B':1, 'C':1}
valid = 2
不满足条件,继续扩展 - 步骤10:right=9, char=’B’
1
2
3
4
5
6
7
8
9当前窗口: [D O B E C O D E B]
操作:
- B在need_dict中
- window_dict['B'] = 2
- window_dict['B'] > need_dict['B'] (2>1),valid不变
状态:
window_dict: {'A':0, 'B':2, 'C':1}
valid = 2
不满足条件,继续扩展 - 步骤11:right=10, char=’A’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70当前窗口: [D O B E C O D E B A]
操作:
- A在need_dict中
- window_dict['A'] = 1
- window_dict['A'] == need_dict['A'] (1==1) → valid=3
状态:
window_dict: {'A':1, 'B':2, 'C':1}
valid = 3 (A、B、C都满足要求)
满足条件,开始收缩窗口
// 收缩窗口
当前窗口长度: 11-1=10
10 > min_len=6,不更新最小窗口
移除left=1的字符'D':
- D不在need_dict中,跳过
状态:
window_dict: {'A':1, 'B':2, 'C':1}
valid = 3
满足条件,继续收缩
当前窗口长度: 11-2=9
9 > min_len=6,不更新最小窗口
移除left=2的字符'O':
- O不在need_dict中,跳过
状态:
window_dict: {'A':1, 'B':2, 'C':1}
valid = 3
满足条件,继续收缩
当前窗口长度: 11-3=8
8 > min_len=6,不更新最小窗口
移除left=3的字符'B':
- B在need_dict中
- window_dict['B'] = 1
- window_dict['B'] == need_dict['B'] (1==1),valid不变
- window_dict['B'] = 1
状态:
window_dict: {'A':1, 'B':1, 'C':1}
valid = 3
满足条件,继续收缩
当前窗口长度: 11-4=7
7 > min_len=6,不更新最小窗口
移除left=4的字符'E':
- E不在need_dict中,跳过
状态:
window_dict: {'A':1, 'B':1, 'C':1}
valid = 3
满足条件,继续收缩
当前窗口长度: 11-5=6
6 == min_len=6,不更新最小窗口
移除left=5的字符'C':
- C在need_dict中
- window_dict['C'] == need_dict['C'] (1==1) → valid=2
- window_dict['C'] = 0
状态:
window_dict: {'A':1, 'B':1, 'C':0}
valid = 2
不满足条件,停止收缩 - 步骤12:right=11, char=’N’
1
2
3
4
5
6
7当前窗口: [O D E B A N]
操作:
- N不在need_dict中,跳过
状态:
window_dict: {'A':1, 'B':1, 'C':0}
valid = 2
不满足条件,继续扩展 - 步骤13:right=12, char=’C’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56当前窗口: [O D E B A N C]
操作:
- C在need_dict中
- window_dict['C'] = 1
- window_dict['C'] == need_dict['C'] (1==1) → valid=3
状态:
window_dict: {'A':1, 'B':1, 'C':1}
valid = 3 (A、B、C都满足要求)
满足条件,开始收缩窗口
// 收缩窗口
当前窗口长度: 13-6=7
7 > min_len=6,不更新最小窗口
移除left=6的字符'O':
- O不在need_dict中,跳过
状态:
window_dict: {'A':1, 'B':1, 'C':1}
valid = 3
满足条件,继续收缩
当前窗口长度: 13-7=6
6 == min_len=6,不更新最小窗口
移除left=7的字符'D':
- D不在need_dict中,跳过
状态:
window_dict: {'A':1, 'B':1, 'C':1}
valid = 3
满足条件,继续收缩
当前窗口长度: 13-8=5
5 < min_len=6,更新最小窗口: min_len=5, start=8
移除left=8的字符'E':
- E不在need_dict中,跳过
状态:
window_dict: {'A':1, 'B':1, 'C':1}
valid = 3
满足条件,继续收缩
当前窗口长度: 13-9=4
4 < min_len=5,更新最小窗口: min_len=4, start=9
移除left=9的字符'B':
- B在need_dict中
- window_dict['B'] == need_dict['B'] (1==1) → valid=2
- window_dict['B'] = 0
状态:
window_dict: {'A':1, 'B':0, 'C':1}
valid = 2
不满足条件,停止收缩 - 最终结果
1
2最小窗口: s[9:9+4] = "BANC"
返回结果: "BANC"
java版
1 | import java.util.HashMap; |
Comment
匿名评论隐私政策









