Skip to content

Commit 2a894a6

Browse files
committed
add notes
1 parent 540c627 commit 2a894a6

3 files changed

Lines changed: 268 additions & 17 deletions

File tree

Algorithm/32.串联所有单词的子串.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,165 @@ s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺
5050

5151
### 思路
5252

53+
##### 方法一,从头到尾遍历s
54+
55+
- words中每个字符的长度是m、words整个数组的长度是n
56+
- 那我们每次可以从s中从头开始去遍历,每次是取 `m*n` 个字符(窗口)
57+
- 然后把这个`m*n`个字符按照words中每个字符的长度m进行分割,然后把分割后的单词与words中的单词进行比较
58+
- 如果和words中的都一样,那当前的索引就是。否则不是
59+
- 在与words中的单词进行比较的时候,这个时候是不用关心顺序的,所以我们可以用一个哈希表来表示单词以及频次
60+
- 当窗口中出现一个单词时,我们就把该单词加到哈希表中,如果已经存在,那就在后面的值+1
61+
- 然后用哈希表中的内容与words中的单词进行对比,如果words中出现一个单词且也在哈希表中,那就将哈希表中该单词的频次减1,如果是0了就一次该单词
62+
- 等到最后都遍历完,如果哈希表的长度是0,那就说明完全与words重点额一样
63+
64+
```python
65+
66+
class Solution:
67+
68+
def findSubstring(self, s: str, words: List[str]) -> List[int]:
69+
res = []
70+
if len(s) == 0 or len(words) == 0 or len(words[0]) == 0:
71+
return[]
72+
73+
wordsLength = len(words[0])
74+
slideLength = wordsLength * len(words)
75+
wordsCount = len(words)
76+
77+
wordsMap = dict()
78+
for word in words:
79+
wordsMap[word] = wordsMap.get(word, 0) + 1
80+
81+
tempMap = dict()
82+
for index in range(len(s) - slideLength + 1):
83+
currentString = s[index: index+slideLength]
84+
print(currentString)
85+
for currentStringIndex in range(wordsCount):
86+
currentWord = currentString[currentStringIndex * wordsLength: (currentStringIndex + 1) * wordsLength]
87+
tempMap[currentWord] = tempMap.get(currentWord, 0) + 1
88+
89+
print(tempMap)
90+
print(wordsMap)
91+
if tempMap == wordsMap:
92+
res.append(index)
93+
tempMap.clear()
94+
95+
96+
return res
97+
```
98+
99+
100+
101+
时间复杂度:O(n×m×k),n为s的长度,m为每个words中单词的长度,也就是len(words[0]),k为words的长度,也就是len(words)
102+
103+
104+
空间复杂度:每次循环都新建一个字典tempMap,但循环结束就清除,所以空间为O(m)(m是words中不同单词的个数)?实际上每次循环都重新统计,所以空间上是O(m),但要注意,我们每次循环都重新统计整个子串,所以没有利用滑动窗口的特性。
105+
106+
107+
而下面方法二的时间复杂度是:
108+
- O(n×k): n为s的长度,k为words中单词的个数
109+
110+
111+
112+
##### 方法二
113+
114+
115+
上面方法一种当仅使用一个从0开始的滑动窗口时,为了避免漏掉一些子串,在缩短窗口时,每次只能缩小一格,而且这还会导致窗口内所维护的单词计数无效。那么,希望有一个方法,可以保证:
116+
- 不遗漏子串
117+
- 缩小窗口时,不会使窗口内的单词计数无效
118+
119+
那么,在使用滑动窗口时,每次都移动 sz = len(words[0]) 个字符,那么我们就可以按照单词的纬度进行统计。
120+
但是,这样会导致某一些子串没有枚举到(即[1, sz-1]为起点的子串都被忽略了),所以为了保证不遗漏子串,可以枚举以[0, sz-1]为起点的所有滑动窗口,并且每一个滑窗都是互相独立的。
121+
122+
---
123+
124+
- 建立滑动窗口
125+
126+
- 如何建立:计算窗口长度为:words中所有串拼接后的长度len,第一个窗口为[0,len-1]
127+
- 如果窗口中的字符串和words中所有串拼接后相等,则说明满足要求。
128+
- 如何判断:
129+
- 将words中的所有word放入hashmap。key为word,value为个数。
130+
- 对窗口进行substr操作,每隔d,substr一次。d为words中word的长度。将substr的结果作为key放入另一个hashmap,个数为value。
131+
- 当窗口中没有剩余字符时,对两个map进行判断,如果相等。说明满足要求。此时记录窗口的起点。
132+

133+
- 建立多起点的滑动窗口。
134+
135+
- 何为多起点:
136+
- 一般理解滑动窗口从0或者某个数值开始,向右滑动不断滑动一个步长。
137+
- 此处需要建立多个滑动窗口,数量为d。起点分别为0,1,2...d;
138+
139+
- 为何需要多起点:
140+
- 如果只建立一个滑动窗口,那么每次就只能滑动一格,因为需要找到所有的可能。但是这样的操作意味着之前建立的map需要重新构建
141+
- 例如:foobarfoobar [foo][bar]。当foobar完成匹配后,向右滑动一格,oobarf。这时候需要重新构建map。插入oob ,arf。时间复杂度很高,而滑动窗口应该是线性时间复杂度,
142+
- 理想状态是,滑动d格,删去左侧foo,加入右侧foo。这个时候不需要重新构建map,map中原本的foo的计数先-1再+1即可,然后判断即可。
143+

144+
那么如果按照上面的方法,每次都滑动d,又会漏检。例如afoobarfoobar [foo][bar]
145+
146+
因此,需要多起点,afoobar,foobar。。个数为d个。这样每个窗口每次都是滑动d格。map的效率最高
147+
148+
- 如何建立多起点
149+
150+
- 从0开始初始化d个滑动窗口。每个滑动窗口每次都是滑动d格。建立相应的map。
151+
152+
- 最后得到vector<map<string, int>>;
153+

154+
- 滑动
155+
156+
- 由于已经建立了多起点的滑动窗口,所以不会存在漏检的情况。同时map的效率最高。
157+
158+
159+

160+
161+
162+
163+
```java
164+
165+
class Solution {
166+
public List<Integer> findSubstring(String s, String[] words) {
167+
// 记录所有满足的结果索引
168+
List<Integer> res = new ArrayList<>();
169+
if (s == null || s.length() == 0 || words == null || words.length == 0) {
170+
return res;
171+
}
172+
173+
HashMap<String, Integer> map = new HashMap<>();
174+
// 每个单词的长度是固定的
175+
int one_word = words[0].length();
176+
int word_num = words.length;
177+
// 窗口长度
178+
int all_len = one_word * word_num;
179+
// 把words中的内容和次数都记录到HashMap中
180+
for (String word : words) {
181+
map.put(word, map.getOrDefault(word, 0) + 1);
182+
}
183+
184+
for (int i = 0; i < one_word; i++) {
185+
int left = i, right = i, count = 0;
186+
HashMap<String, Integer> tmp_map = new HashMap<>();
187+
while (right + one_word <= s.length()) {
188+
String w = s.substring(right, right + one_word);
189+
right += one_word;
190+
if (!map.containsKey(w)) {
191+
count = 0;
192+
left = right;
193+
tmp_map.clear();
194+
} else {
195+
tmp_map.put(w, tmp_map.getOrDefault(w, 0) + 1);
196+
count++;
197+
while (tmp_map.getOrDefault(w, 0) > map.getOrDefault(w, 0)) {
198+
String t_w = s.substring(left, left + one_word);
199+
count--;
200+
tmp_map.put(t_w, tmp_map.getOrDefault(t_w, 0) - 1);
201+
left += one_word;
202+
}
203+
if (count == word_num) res.add(left);
204+
}
205+
}
206+
}
207+
return res;
208+
}
209+
}
210+
```
211+
53212

54213

55214
---

Algorithm/33..md

Lines changed: 0 additions & 17 deletions
This file was deleted.

Algorithm/33.最小覆盖子串.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
33.最小覆盖子串
2+
===
3+
4+
5+
### 题目
6+
7+
给你一个字符串s、一个字符串t。返回s中涵盖t所有字符的最小子串。如果s中不存在涵盖t所有字符的子串,则返回空字符串 "" 。
8+
9+
10+
11+
注意:
12+
13+
- 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
14+
- 如果 s 中存在这样的子串,我们保证它是唯一的答案。
15+
16+
17+
示例 1:
18+
19+
- 输入:s = "ADOBECODEBANC", t = "ABC"
20+
- 输出:"BANC"
21+
- 解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
22+
23+
示例 2:
24+
25+
- 输入:s = "a", t = "a"
26+
- 输出:"a"
27+
- 解释:整个字符串 s 是最小覆盖子串。
28+
29+
示例 3:
30+
31+
- 输入: s = "a", t = "aa"
32+
- 输出: ""
33+
- 解释: t 中两个字符 'a' 均应包含在 s 的子串中,因此没有符合条件的子字符串,返回空字符串。
34+
35+
36+
提示:
37+
38+
- m == s.length
39+
- n == t.length
40+
- 1 <= m, n <= 105
41+
- s 和 t 由英文字母组成
42+
43+
44+
进阶:你能设计一个在 o(m+n) 时间内解决此问题的算法吗?
45+
46+
### 思路
47+
48+
题目中说如果 s 中存在这样的子串,我们保证它是唯一的答案。
49+
50+
双指针
51+
52+
- 初始化ansLeft=−1, ansRight=m,用来记录最短子串的左右端点,其中m是s的长度。
53+
- 用一个哈希表(或者数组)cntT 统计 t 中每个字母的出现次数。
54+
- 初始化 left=0,以及一个空哈希表(或者数组)cntS,用来统计 s 子串中每个字母的出现次数。
55+
- 遍历 s,设当前枚举的子串右端点为 right,把 s[right] 的出现次数加一。
56+
- 遍历 cntS 中的每个字母及其出现次数,如果出现次数都大于等于 cntT 中的字母出现次数:
57+
- 如果`right−left<ansRight−ansLeft`,说明我们找到了更短的子串,更新 ansLeft=left, ansRight=right。
58+
- 把 s[left] 的出现次数减一。
59+
- 左端点右移,即 left 加一。
60+
重复上述三步,直到 cntS 有字母的出现次数小于 cntT 中该字母的出现次数为止。
61+
62+
最后,如果 ansLeft<0,说明没有找到符合要求的子串,返回空字符串,否则返回下标 ansLeft 到下标 ansRight 之间的子串。
63+
由于本题大写字母和小写字母都有,为了方便,代码实现时可以直接创建大小为 128 的数组,保证所有 ASCII 字符都可以统计。
64+
65+
66+
```java
67+
class Solution {
68+
public String minWindow(String s, String t) {
69+
Map<Character, Integer> cnt = new HashMap<>();
70+
for (char c : t.toCharArray()) {
71+
cnt.put(c, cnt.getOrDefault(c, 0) + 1);
72+
}
73+
int ans_l = -1;
74+
int ans_r = s.length();
75+
int l = 0;
76+
int count = cnt.size();
77+
for (int r = 0; r < s.length(); r++) {
78+
char c = s.charAt(r);
79+
if (cnt.containsKey(c)) {
80+
cnt.put(c, cnt.get(c) - 1);
81+
if (cnt.get(c) == 0) {
82+
count--;
83+
}
84+
}
85+
while (count == 0) {
86+
if (ans_r - ans_l > r - l) {
87+
ans_l = l;
88+
ans_r = r;
89+
}
90+
char ch = s.charAt(l);
91+
if (cnt.containsKey(ch)) {
92+
if (cnt.get(ch) == 0) {
93+
count++;
94+
}
95+
cnt.put(ch, cnt.get(ch) + 1);
96+
}
97+
l++;
98+
}
99+
}
100+
return ans_l == -1 ? "" : s.substring(ans_l, ans_r+1);
101+
}
102+
}
103+
```
104+
105+
---
106+
- 邮箱 :charon.chui@gmail.com
107+
- Good Luck!
108+
109+

0 commit comments

Comments
 (0)