leetcode --- 30. 串联所有单词的子串[C++ 滑动窗口/双指针]

解决LeetCode中字符串异位词串联子串问题,通过滑动窗口和哈希表,找出符合条件的子串。涉及频率统计和窗口操作优化。

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

原题:30. 串联所有单词的子串 - 力扣(LeetCode)

题目解析:

本题在这道题的算法原理基础上进行思考会简单许多

leetcode --- 438. 找到字符串中所有字母异位词[C++/滑动窗口+哈希表]-优快云博客

关键信息---words中的字符串长度都相等

要在s字符串中找到串联子串。将每一个words中的字符串看做一个字符

比如示例1中,foo看作a,bar看作b

那么s字符串就可以看成是 bacabd (the看作c,man看作d)

那么这道题就变成了和438一样的寻找异位词的问题。

但是这道题还有些不同的地方

比如barfoothefoobarman这个s字符串

除了从b开始的分割方式,还可能存在从第二个字符开始的分割方式,和第三个字符开始的分割方式,这几个分割方式中都可能出现符合题目要求的串联子串,只是示例1恰好没有。但是我们编写代码时要考虑到这个情况。

算法原理:

与438这道题不同的是:

1.这道题的哈希表要存的是字符串出现的频次;

2.此外,指针的移动方式也不同,每次移动的长度应该等于words中的字符串的长度;

3.还有一个不同就是题目解析中说的不同分割方式,这中不同在算法中的体现就是滑动窗口的执行次数。根据题目解析中的规律,当起始的字符跳过了一个words字符串的长度,那么就会和第一种情况(从第一个字符开始的分割方式)重复,所以滑动窗口的执行次数就是words字符串长度的大小。

先创建第一个哈希表hash1来保存words中字符出现的频次

再创建一个哈希表hash2用来保存窗口中字符出现的频次

滑动窗口四步走

1.进窗口 + 维护count

更新哈希表hash2,判断进窗口的字符串是否为有效字符串

有效则count++

2.判断

窗口长度大于words字符串总长度时出窗口

3.出窗口

判断出窗口的字符串是否为有效字符串

有效则count--

然后更新哈希表hash2,把出窗口的字符频次减掉

然后left指针右移

4.更新状态

当有效字符串个数等于words中的字符串个数

将left下标给返回值

以上步骤总共执行len次,len为words中一个字符串的长度

每一次执行的时候,left和right的起始位置要有相应变化

代码编写:

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        unordered_map<string,int>hash1; //保存words里所有单词出现的频次
        for(auto &s : words)
        {
            hash1[s]++;
        }

        vector<int>ret;
        int len = words[0].size(),m = words.size();
        for(int i = 0;i<len;i++)//滑动窗口执行len次
        {
            unordered_map<string,int>hash2;//保存窗口内所有单词出现的频次

                //right是其中一个字符串的起始位置,其长度要合法且符合要求
                //所以循环的结束条件为right+len <= s.size()
                //count为有效字符串个数
                //注意left和right的起始位置要随滑动窗口执行次数变化而变化
            for(int left = i,right = i,count = 0;right + len <= s.size(); right+=len)
            {
                //进窗口 + 维护count
                string in = s.substr(right,len);
                if(++hash2[in] <= hash1[in])
                {
                    count++;
                }

                //判断
                if(right - left + 1 > len*m)
                {
                string out = s.substr(left,len);
                //出窗口    
                if(hash2[out]-- <= hash1[out])
                {
                    count--;
                }
                left+=len;//出完窗口left要移动
                }
                //更新状态
                if(count == m) //有效字符串个数等于words中字符串个数
                {
                    ret.push_back(left);
                }
            }
        }
        return ret;
    }
};


在时间上进一步优化

因为s字符串中可能出现words字符串中没有的字符,所以在进行比较的时候,hash1会先创建出一个空间再进行比较,这样就造成一定程度的时间消耗,所以在进行比较之前加个判断,hash1.count(in)判断这个字符串是否存在,如果不存在于words中,那就没有判断的必要,不影响有效字符串个数的大小。

优化地方在下方注释标出

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        unordered_map<string,int>hash1; //保存words里所有单词出现的频次
        for(auto &s : words)
        {
            hash1[s]++;
        }

        vector<int>ret;
        int len = words[0].size(),m = words.size();
        for(int i = 0;i<len;i++)//滑动窗口执行len次
        {
            unordered_map<string,int>hash2;//保存窗口内所有单词出现的频次

                //right是其中一个字符串的起始位置,其长度要合法且符合要求
                //所以循环的结束条件为right+len <= s.size()
                //count为有效字符串个数
                //注意left和right的起始位置要随滑动窗口执行次数变化而变化
            for(int left = i,right = i,count = 0;right + len <= s.size(); right+=len)
            {
                //进窗口 + 维护count
                string in = s.substr(right,len);
                if(hash1.count(in) && ++hash2[in] <= hash1[in]) //优化
                {
                    count++;
                }

                //判断
                if(right - left + 1 > len*m)
                {
                string out = s.substr(left,len);
                //出窗口    
                if(hash1.count(out) && hash2[out]-- <= hash1[out])  //优化
                {
                    count--;
                }
                left+=len;//出完窗口left要移动
                }
                //更新状态
                if(count == m) //有效字符串个数等于words中字符串个数
                {
                    ret.push_back(left);
                }
            }
        }
        return ret;
    }
};

### 使用滑动窗口算法解决串联所有单词子串 #### 解决方案概述 为了处理 LeetCode30 题的要求,即在字符串 `s` 中寻找由字符串数组 `words` 组成的所有可能的连续子串的位置,采用滑动窗口方法是一种高效的选择[^1]。 #### 参数定义与初始化 设定了几个重要的参数来辅助解决问题: - 设 `m = words.length` 表示单词的数量; - 设 `len` 是 `words` 数组中单个单词的长度,则整个窗口宽度应设定为 `len * m`;这代表了当我们在字符串 `s` 上应用此窗口时所期望匹配到的一系列连续字符总长度[^2]。 #### 实现思路 通过内外两层嵌套的方式实现滑动窗口机制: 外层循环负责遍历起点位置的变化,每次前进步长设置为单一单词长度(`len`)。对于每一个新的起始点而言, 内层则利用固定大小等于全部目标短语拼接后的整体尺寸(`len*m`)作为扫描单位,在当前选定范围内逐一对比是否存在符合条件的结果集[^4]。 具体操作如下所示: ```python from collections import Counter, defaultdict def findSubstring(s: str, words: List[str]) -> List[int]: if not s or not words: return [] word_length = len(words[0]) total_words = len(words) window_size = word_length * total_words # 记录每个单词出现次数 word_count = Counter(words) result_indices = [] for i in range(word_length): start = i current_word_counts = defaultdict(int) matched_words = 0 for j in range(i, len(s) - word_length + 1, word_length): substring = s[j:j + word_length] if substring in word_count: current_word_counts[substring] += 1 while current_word_counts[substring] > word_count[substring]: temp_start_str = s[start:start + word_length] current_word_counts[temp_start_str] -= 1 if current_word_counts[temp_start_str] >= word_count[temp_start_str]: matched_words -= 1 start += word_length matched_words += 1 if matched_words == total_words: result_indices.append(start) else: start = j + word_length current_word_counts.clear() matched_words = 0 return result_indices ``` 该函数首先检查输入的有效性并计算必要的变量值。接着创建了一个外部for循环用于控制初始偏移量i (范围是从0至word_length),从而确保能够捕捉到任何潜在解法而不遗漏任何一个可能性。内部逻辑则是基于上述提到的方法论构建而成,旨在有效地定位满足条件的索引列表。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值