LeetCode 30. 串联所有单词的子串
题目描述:
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
思路一:
比较直接的想法应该就是采用滑动窗口的方法。因为单词的长度都一样,因此可以设置一个长度为 “单词个数 * 单词长度” 的滑动窗口,在给定的字符串 s 上进行滑动,因为单词的长度一样,因此可以对窗口内的子串进行等位划分出多个单词,判断每个划分出来的单词是否在单词表里,如果单词在的个数和单词表的个数一致,则记录窗口左端的起始位置,否则窗口向后移动一位
这里要需要考虑单词重复或者单词首尾重叠的问题,因此可以弄一个单词表的暂存数组,在检索单词是否存在时,若存在则将该单词从占存数组中删去,若不存在则 break 后移滑动窗口 (加了 break 才勉强过)
勉勉强强过了,压着时间线过的
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
index = []
words_len = len(words)
if words_len == 0:
return index
word_len = len(words[0])
winsize = word_len * words_len
start = 0
while start + winsize <= len(s):
has = 0
tmp_words = words.copy() # 暂存数组
substr = s[start : start + winsize]
for i in range(words_len):
sub_word = substr[i * word_len : ( i + 1 ) * word_len]
if sub_word in tmp_words:
has += 1
del tmp_words[tmp_words.index(sub_word)] # 如果直接remove,相同的单词会全删掉
else:
break
if has == words_len:
index.append(start)
start += 1
return index
考虑到时间用的比较长,自己没什么思路,看了下大神们的题解,基本上都是滑动窗口的思路,但是可以在滑动窗口的基础上进行时间上的优化
比如 powcai 的思路就是,在字符串 s 上维护一个所有单词总和的长度的队列,即初始先统计出单词表中各个单词出现的个数,在对字符串 s 进行扫描时,先固定左窗口位置不变,右窗口位置不断向后扫描一个单词的长度,对扫描到的单词 w 首先判断其是否在单词表内,若不在则左窗口移动到当前右窗口的位置,反之则用一个 Counter 记录下在当前滑动窗口内该单词出现的次数,若其出现的次数超过了单词表中出现的次数,则循环将左窗口右移一个单词的长度,并更新左端被移除的单词 left_w 的次数,直到 w 在当前统计的次数中小于单词表的次数;若此时窗口内的单词数与单词表中的单词数相同,则记录下左窗口的位置
当然上述的窗口滑动还不能覆盖所有情况,因此需要在最外层添加一个 for 循环,使窗口从 (0 -> len(one_word)) 的位置作为起始位置开始扫描,便可以覆盖掉所有情况(因为单词长度都一致,所以只需要考虑从前 len(one_word) 的位置作为初始位置开始即可,后面的位置在前面的位置扫描中有重复)
附 powcai 的代码:
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
from collections import Counter
if not s or not words:return []
one_word = len(words[0])
word_num = len(words)
n = len(s)
if n < one_word:return []
words = Counter(words)
res = []
for i in range(0, one_word):
cur_cnt = 0 # 统计当前窗口中单词的个数
left = i
right = i
cur_Counter = Counter()
while right + one_word <= n:
w = s[right:right + one_word]
right += one_word
if w not in words:
left = right
cur_Counter.clear()
cur_cnt = 0
else:
cur_Counter[w] += 1
cur_cnt += 1
while cur_Counter[w] > words[w]:
left_w = s[left:left+one_word]
left += one_word
cur_Counter[left_w] -= 1
cur_cnt -= 1
if cur_cnt == word_num :
res.append(left)
return res
作者:powcai
链接:https://leetcode-cn.com/problems/two-sum/solution/chuan-lian-suo-you-dan-ci-de-zi-chuan-by-powcai/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
附一个跟上述思路一样的C++版本代码:
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
unordered_map<string, int> word_map; // unordered_map 采用hash实现,查找快; map内部采用红黑树,有序。
vector<int> result;
if (s=="" || words.size()==0)
return result;
int word_len = words[0].size();
int word_num = words.size();
if (s.size() < word_len * word_num)
return result;
for (int i=0;i<word_num;i++) {
if (word_map.count(words[i]) == 0)
word_map[words[i]] = 1;
else
word_map[words[i]]++;
}
for (int i=0;i<word_len;i++) {
int left = i;
int right = i;
unordered_map<string, int> find_map; // 查找map
int count = 0; // 匹配个数
while (right + word_len <= s.size()) {
string cur = s.substr(right, word_len);
count++;
right += word_len;
if (word_map.count(cur) == 0) { // 遇到不在字典里的字符,直接清空,跳过
count = 0;
find_map.clear();
left = right;
continue;
}
if (find_map.count(cur) == 0)
find_map[cur] = 1;
else
find_map[cur]++;
while (find_map[cur] > word_map[cur]) { // 个数使用过多,区间左半边向前移动
string lefts = s.substr(left, word_len);
left += word_len;
find_map[lefts]--;
count--;
}
if (count == word_num)
result.push_back(left);
}
}
return result;
}
};
作者:ma-xing
链接:https://leetcode-cn.com/problems/two-sum/solution/c-hua-dong-chuang-kou-by-ma-xing/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。