@@ -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---
0 commit comments