Skip to content

Commit 386ba44

Browse files
author
chenjieqing
committed
提交Minimum Window Substring.
1 parent 5e72320 commit 386ba44

File tree

14 files changed

+342
-0
lines changed

14 files changed

+342
-0
lines changed

code/MinimumWindowSubstring/main.cpp

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#include <iostream>
2+
#include <unordered_map>
3+
4+
std::string FindMinimumWindowSubstring(std::string source,
5+
std::string target) {
6+
7+
if (source.empty() || target.empty()) {
8+
return "";
9+
}
10+
11+
size_t source_len = source.length();
12+
size_t target_len = target.length();
13+
14+
if (source_len < target_len) {
15+
return "";
16+
}
17+
18+
size_t start = 0; //符合条件的最小窗口的字符串位置
19+
size_t min_len = UINT_MAX; //符合条件的最小窗口字符长度
20+
21+
//1)初始化
22+
size_t left = 0; //left指针
23+
size_t right = 0; //right指针
24+
25+
std::unordered_map<char, size_t> window_char_map; //记录当前子串对应char
26+
std::unordered_map<char, size_t> target_char_map; //记录目标子串对应char
27+
28+
for (char c : target) {
29+
target_char_map[c]++;
30+
}
31+
32+
size_t target_char_map_len = target_char_map.size(); //target子串中包含字符的个数(去重后)
33+
34+
size_t match = 0; //记录已经匹配的字符个数(去重后)
35+
36+
//4)重复:直到S串末尾
37+
while (right < source_len) {
38+
39+
//2)找到可行窗口,即包含T中所有字符的窗口。
40+
char current_char = source[right];
41+
42+
//如果target包含该字符
43+
if (target_char_map.count(current_char)) {
44+
45+
//在当前子串标记匹配该字符
46+
window_char_map[current_char]++;
47+
48+
if (window_char_map[current_char] == target_char_map[current_char]) {
49+
match++; //累加已经匹配字符个数
50+
}
51+
}
52+
right++; //right指针右移
53+
54+
//3)如果当前窗口可行,则想办法右移left指针。
55+
while (match == target_char_map_len) {
56+
57+
//如果符合条件的最小窗口len发生变化,则更新
58+
if (right - left < min_len) {
59+
60+
//移动记录符合条件的start位置
61+
start = left;
62+
63+
//更新符合条件的最小窗口len
64+
min_len = right - left;
65+
}
66+
67+
char left_char = source[left];
68+
69+
//如果left位置的字符在target中
70+
if (target_char_map.count(left_char)) {
71+
72+
//从当前窗口中减掉left位置的字符
73+
if (window_char_map.count(left_char)) {
74+
window_char_map[left_char]--;
75+
}
76+
77+
//如果left位置的字符,在target中是必须的,则match - 1,这会导致下一个while循环终止,即left指针右移操作终止;
78+
//直到找到可行窗口后,再执行第3)步。
79+
if (window_char_map[left_char] < target_char_map[left_char]) {
80+
if (match > 0) {
81+
match--;
82+
}
83+
}
84+
}
85+
86+
left++; //left指针右移
87+
}
88+
}
89+
90+
return min_len == UINT_MAX ? "" : source.substr(start, min_len);
91+
}
92+
93+
int main(int argc, const char * argv[]) {
94+
95+
std::string source = "";
96+
std::string target = "ABC";
97+
printf("\nsource: %s\ntarget: %s\nMinimum window substring is: %s\n", source.c_str(), target.c_str(), FindMinimumWindowSubstring(source, target).c_str());
98+
99+
source = "ADOBECODEBANC";
100+
target = "";
101+
printf("\nsource: %s\ntarget: %s\nMinimum window substring is: %s\n", source.c_str(), target.c_str(), FindMinimumWindowSubstring(source, target).c_str());
102+
103+
source = "ADOBECODEBANC";
104+
target = "ADOBECODEBANCC";
105+
printf("\nsource: %s\ntarget: %s\nMinimum window substring is: %s\n", source.c_str(), target.c_str(), FindMinimumWindowSubstring(source, target).c_str());
106+
107+
source = "ADOBECODEBANC";
108+
target = "ABC";
109+
printf("\nsource: %s\ntarget: %s\nMinimum window substring is: %s\n", source.c_str(), target.c_str(), FindMinimumWindowSubstring(source, target).c_str());
110+
111+
source = "ADOBECODEBANC";
112+
target = "ABAC";
113+
printf("\nsource: %s\ntarget: %s\nMinimum window substring is: %s\n", source.c_str(), target.c_str(), FindMinimumWindowSubString(source, target).c_str());
114+
115+
source = "ADOBECODEBANC";
116+
target = "BECE";
117+
printf("\nsource: %s\ntarget: %s\nMinimum window substring is: %s\n", source.c_str(), target.c_str(), FindMinimumWindowSubString(source, target).c_str());
118+
119+
source = "ADOBECODEBANC";
120+
target = "ABCAC";
121+
printf("\nsource: %s\ntarget: %s\nMinimum window substring is: %s\n", source.c_str(), target.c_str(), FindMinimumWindowSubString(source, target).c_str());
122+
123+
return 0;
124+
}

doc/MinimumWindowSubstring/ReadMe.md

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
## Minimum Window Substring(最小窗口覆盖子串)
2+
3+
### 一、题目
4+
5+
#### 1. 英文版
6+
7+
【Minimum Window Substring】
8+
Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).
9+
Example:
10+
11+
Input: S = "ADOBECODEBANC", T = "ABC"
12+
Output: "BANC"
13+
14+
Note:
15+
· If there is no such window in S that covers all characters in T, return the empty string "".
16+
· If there is such window, you are guaranteed that there will always be only one unique minimum window in S.
17+
18+
#### 2. 中文版
19+
20+
【最小窗口覆盖子串】
21+
给你一个字符串S、一个字符串T,请在字符串S里面找出:包含T所有字母的最小子串,要求算法的时间复杂度为O(n)。
22+
23+
示例:
24+
输入: S = "ADOBECODEBANC", T = "ABC"
25+
输出: "BANC"
26+
27+
说明:
28+
如果S中不存这样的子串,则返回空字符串""。
29+
如果S中存在这样的子串,我们保证它是唯一的答案。
30+
31+
![](../../res/MinimumWindowSubstring/ABC.png)
32+
33+
### 二、题目理解
34+
35+
> 1.在S(source)中找到包含T(target)中**全部字母**的一个子串;
36+
37+
> 2.子串T中**字符不去重**,即 T = "ABC"和 T = "ABAC"是不同的子串,后者需要找到包含两个A的子串,包含"ABAC"的最小窗口覆盖子串为"ADOBECODEBA";
38+
39+
![](../../res/MinimumWindowSubstring/ABAC.png)
40+
41+
> 3.**顺序无所谓**,但这个子串一定是所有可能子串中最短的。
42+
43+
![](../../res/MinimumWindowSubstring/BECE.png)
44+
45+
### 三、解法
46+
47+
#### 1.暴力解法
48+
49+
```C++
50+
for (int i = 0; i < source.size(); i++)
51+
for (int j = i + 1; j < source.size(); j++)
52+
if source[i:j] 包含 t 的所有字母:
53+
更新答案
54+
```
55+
56+
#### 2.滑动窗口
57+
58+
在滑动窗口类型的问题中都会有两个指针。一个用于延伸现有窗口的right指针,和一个用于收缩窗口的left指针。在任意时刻,只有一个指针运动,而另一个保持静止。
59+
60+
而该题目需要找到包含子串的最小窗口,我们可以通过移动right指针不断扩张窗口。当窗口包含全部所需的字符后,如果能收缩,我们就收缩窗口知道得到最小窗口,最终可以得到最小的可行窗口。
61+
62+
![](../../res/MinimumWindowSubstring/1.png)
63+
64+
![](../../res/MinimumWindowSubstring/2.png)
65+
66+
![](../../res/MinimumWindowSubstring/3.png)
67+
68+
![](../../res/MinimumWindowSubstring/4.png)
69+
70+
![](../../res/MinimumWindowSubstring/5.png)
71+
72+
![](../../res/MinimumWindowSubstring/6.png)
73+
74+
![](../../res/MinimumWindowSubstring/7.png)
75+
76+
![](../../res/MinimumWindowSubstring/8.png)
77+
78+
> 1)初始化:left和right指针都指向S的第一个元素A;
79+
80+
> 2)移动right指针:向右扩张,直到得到一个可行窗口;
81+
82+
> 3)得到可行的窗口后,将left指针逐个右移,如果得到窗口依然可行,则更新最小窗口大小;
83+
84+
> 4)如果窗口不再可行,则跳转到第2)步;
85+
86+
> 5)重复以上步骤,直到遍历完S。
87+
88+
重点关注:
89+
我们更高效地判断第2)步,即判断当前left和right指向的窗口是否可行,也就是是否包含T?
90+
91+
![](../../res/MinimumWindowSubstring/0.png)
92+
93+
代码:(C++)
94+
```C++
95+
#include <iostream>
96+
#include <unordered_map>
97+
98+
std::string FindMinimumWindowSubstring(std::string source,
99+
std::string target) {
100+
101+
if (source.empty() || target.empty()) {
102+
return "";
103+
}
104+
105+
size_t source_len = source.length();
106+
size_t target_len = target.length();
107+
108+
if (source_len < target_len) {
109+
return "";
110+
}
111+
112+
size_t start = 0; //符合条件的最小窗口的字符串位置
113+
size_t min_len = UINT_MAX; //符合条件的最小窗口字符长度
114+
115+
//1)初始化
116+
size_t left = 0; //left指针
117+
size_t right = 0; //right指针
118+
119+
std::unordered_map<char, size_t> window_char_map; //记录当前子串对应char
120+
std::unordered_map<char, size_t> target_char_map; //记录目标子串对应char
121+
122+
for (char c : target) {
123+
target_char_map[c]++;
124+
}
125+
126+
size_t target_char_map_len = target_char_map.size(); //target子串中包含字符的个数(去重后)
127+
128+
size_t match = 0; //记录已经匹配的字符个数(去重后)
129+
130+
//4)重复:直到S串末尾
131+
while (right < source_len) {
132+
133+
//2)找到可行窗口,即包含T中所有字符的窗口。
134+
char current_char = source[right];
135+
136+
//如果target包含该字符
137+
if (target_char_map.count(current_char)) {
138+
139+
//在当前子串标记匹配该字符
140+
window_char_map[current_char]++;
141+
142+
if (window_char_map[current_char] == target_char_map[current_char]) {
143+
match++; //累加已经匹配字符个数
144+
}
145+
}
146+
right++; //right指针右移
147+
148+
//3)如果当前窗口可行,则想办法右移left指针。
149+
while (match == target_char_map_len) {
150+
151+
//如果符合条件的最小窗口len发生变化,则更新
152+
if (right - left < min_len) {
153+
154+
//移动记录符合条件的start位置
155+
start = left;
156+
157+
//更新符合条件的最小窗口len
158+
min_len = right - left;
159+
}
160+
161+
char left_char = source[left];
162+
163+
//如果left位置的字符在target中
164+
if (target_char_map.count(left_char)) {
165+
166+
//从当前窗口中减掉left位置的字符
167+
if (window_char_map.count(left_char)) {
168+
window_char_map[left_char]--;
169+
}
170+
171+
//如果left位置的字符,在target中是必须的,则match - 1,这会导致下一个while循环终止,即left指针右移操作终止;
172+
//直到找到可行窗口后,再执行第3)步。
173+
if (window_char_map[left_char] < target_char_map[left_char]) {
174+
if (match > 0) {
175+
match--;
176+
}
177+
}
178+
}
179+
180+
left++; //left指针右移
181+
}
182+
}
183+
184+
return min_len == UINT_MAX ? "" : source.substr(start, min_len);
185+
}
186+
187+
int main(int argc, const char * argv[]) {
188+
189+
std::string source = "ADOBECODEBANC";
190+
std::string target = "ABC";
191+
printf("\nsource: %s\ntarget: %s\nMinimum window substring is: %s\n", source.c_str(), target.c_str(), FindMinimumWindowSubstring(source, target).c_str());
192+
193+
source = "ADOBECODEBANC";
194+
target = "ABAC";
195+
printf("\nsource: %s\ntarget: %s\nMinimum window substring is: %s\n", source.c_str(), target.c_str(), FindMinimumWindowSubstring(source, target).c_str());
196+
197+
source = "ADOBECODEBANC";
198+
target = "BECE";
199+
printf("\nsource: %s\ntarget: %s\nMinimum window substring is: %s\n", source.c_str(), target.c_str(), FindMinimumWindowSubstring(source, target).c_str());
200+
201+
source = "ADOBECODEBANC";
202+
target = "ABCAC";
203+
printf("\nsource: %s\ntarget: %s\nMinimum window substring is: %s\n", source.c_str(), target.c_str(), FindMinimumWindowSubstring(source, target).c_str());
204+
205+
return 0;
206+
}
207+
```
208+
209+
#### 3.算法的时间复杂度
210+
211+
> O(M + N),M和N分别是S和T的长度;
212+
> 其中初始化T每个字符出现个数,时间复杂度为O(N);
213+
> 遍历S while循环为M次,里面嵌套的while循环总计最多为M次,时间复杂度为O(M)。
214+
215+
### 四、参考资料
216+
> 1.[LeetCode最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring)
217+
> 2.[查找包含子串的最小子串](https://www.geeksforgeeks.org/find-the-smallest-window-in-a-string-containing-all-characters-of-another-string/)
218+

res/MinimumWindowSubstring/0.png

158 KB
Loading

res/MinimumWindowSubstring/1.png

149 KB
Loading

res/MinimumWindowSubstring/2.png

223 KB
Loading

res/MinimumWindowSubstring/3.png

121 KB
Loading

res/MinimumWindowSubstring/4.png

120 KB
Loading

res/MinimumWindowSubstring/5.png

136 KB
Loading

res/MinimumWindowSubstring/6.png

119 KB
Loading

res/MinimumWindowSubstring/7.png

111 KB
Loading

res/MinimumWindowSubstring/8.png

112 KB
Loading

res/MinimumWindowSubstring/ABAC.png

167 KB
Loading

res/MinimumWindowSubstring/ABC.png

167 KB
Loading

res/MinimumWindowSubstring/BECE.png

130 KB
Loading

0 commit comments

Comments
 (0)