LeetCode 30.串联所有单词的子串

通过滑动窗口解决LeetCode 30题,找到字符串s中所有由words单词完全匹配的子串起始位置。考虑单词重复和重叠,使用队列优化时间复杂度,从不同起始位置开始扫描以覆盖所有情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值