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

题目描述

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。

注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。(word中单词只出现一次)

示例 1:
输入:
s = “barfoothefoobarman”,
words = [“foo”,“bar”]
输出:[0,9]
解释:从索引 0 和 9 开始的子串分别是 “barfoo” 和 “foobar” 。输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:
s = “wordgoodgoodgoodbestword”,
words = [“word”,“good”,“best”,“word”]
输出:[]

题解

滑动窗口

  • 因为单词的长度 len 是固定的,可将一个单词看成一个单元
  • 对单词使用滑动窗口,每次步长就是 len
  • 在 0 - len 的范围内,每一个都作为滑动窗口的起点,滑动 len 次,即可覆盖所有字符串的各种组合
    LeetCode30
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        if(words.size() == 0) return {};
        unordered_map<string, int> wordcount;
        for(auto &w:words) wordcount[w]++;//统计words中每个单词次数
        int len = words[0].size();//words中每个单词等长,计算其单词长度
        vector<int> ans;
        for(int i = 0; i < len; i++){//在 0 - len 的范围内,每一个都作为滑动窗口的起点,滑动 len 次,即可覆盖所有字符串的各种组合
            int left = i;
            int right = left;
            int count = 0;

            unordered_map<string, int> window;
            while(left + words.size()*len <= s.size()){
                string temp = "";
                while(count < words.size()){//判断当前滑动窗口window中元素是否由words中单词组成
                    temp = s.substr(right, len);//子串是在字符位置right开始,跨越len个字符
                    if(wordcount.find(temp) == wordcount.end() || window[temp] >= wordcount[temp]) break;//wordcount中没有子串temp,或者window中子串temp的数量大于wors中相应单词数量,则退出while循环,说明当前window中元素不满足条件
                    window[temp]++;//计算当前window中单词temp次数
                    count++;
                    right += len;//在当前window中以单个单词长度遍历
                }
                if(window == wordcount) ans.push_back(left);//如果window中单词及其出现次数与words一样,记录遍历起始位置
                //此时temp字串在words中,window遍历时向右移动words中单个单词长度,进行下一轮遍历
                if(wordcount.find(temp) != wordcount.end()){
                    window[s.substr(left, len)]--;
                    count--;
                    left += len;
                }
                else{//此时temp字串不在words中,window遍历时会跳过当前子串,以words中单个单词长度向右移动,进行下一轮遍历,比如图示中的xba子串
                    right += len;
                    left = right;
                    count = 0;
                    window.clear();
                }
            }
        }
        return ans;
    }
};

复杂度分析

  • 时间复杂度: O(n2)
  • 空间复杂度: O(n)
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> result;
        if(s.empty() || words.empty()) return result;
    
        // 单词数组中的单词的大小,个数以及总长度
        int word_len=words[0].size(), word_num=words.size();
        int all_len=word_len*word_num;
        unordered_map<string, int> map;  //建立单词->单词个数的映射
        for(const auto& w : words)
            map[w]++;
        
        for(int i=0; i<word_len; ++i) {
            // left和rigth表示窗口的左右边界,count用来统计匹配的单词个数
            int left=i, right=i, count=0;
            unordered_map<string, int> tmp;
            
            // 开始滑动窗口
            while(right+word_len <= s.size()) {
                // 从s中提取一个单词拷贝到w
                string w=s.substr(right, word_len);
                right += word_len;  // 窗口右边界右移一个单词的长度
                if(map.count(w)==0) { // 此单词不在words中
                    count=0;
                    left=right;
                    tmp.clear();
                }
                else {  // 该单词匹配成功,添加到tmp中
                    tmp[w]++;
                    count++;    
                    while(tmp.at(w) > map.at(w)) {  // 一个单词匹配多次
                        string t_w=s.substr(left,word_len);
                        count--;
                        tmp[t_w]--;
                        left+=word_len;
                    }
                    if(count==word_num)  result.push_back(left);
                }
            }
        }
        return result;
    }
};

参考

【串联所有单词的子串】循环 len 次滑动窗口
python三种方法逐渐优化 击败99.9%
C++ 滑动窗口 + 字符串哈希
串联所有单词的子串

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值