leetcode 30,串联所有单词字串,(动态窗口总结)

30. 串联所有单词的子串
给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。
s 中的 串联子串 是指一个包含  words 中所有字符串以任意顺序排列连接起来的子串。
例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", 
"abefcd","cdabef", "cdefab","efabcd", 和 "efcdab" 都是串联子串。 
"acdbef" 不是串联子串,因为他不是任何 words 排列的连接。
返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。
示例 1:
输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:因为 words.length == 2 同时 words[i].length == 3,
连接的子字符串的长度必须为 6。
子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。
子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。
输出顺序无关紧要。返回 [9,0] 也是可以的。
示例 2:
输入:s = "wordgoodgoodgoodbestword", 
words = ["word","good","best","word"]
输出:[]
解释:因为 words.length == 4 并且 words[i].length == 4,
所以串联子串的长度必须为 16。
s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。
所以我们返回一个空数组。

题解:

介绍动态窗口

滑动窗口考察的是「综合能力」

窗口的数据结构:应根据各种数据结构的特点来选取

哈希表,unordered_map
优先队列,priority_queue
红黑树,multiset、set、multimap、map
单调队列,deque实现
应用条件:

原数据必须满足单调性,不满足的可以用前缀和。例如力扣 560. 和为 K 的子数组原数据中含有负数,破坏了单调性
模板 (c++)

void slidingWindow(string s, string t) {
    int left = 0;
    int right = 0;
    int valid = 0; // 窗口内已凑齐的字符种类数量
    unordered_map<char, int> window;
    unordered_map<char, int> need; // 需要凑齐的字符和对应数量
    for (char c : t) need[c]++;

    while (right < s.size()) {
        // 右边届入窗口,进行窗口内数据的一系列更新
        window[s[right]]++;
        if (window[s[right]] == need[s[right]]) {
            valid++;
        }  // 注意:先加,再判断

        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
            // 左边界移出窗口,进行窗口内数据的一系列更新
            if (window[s[left]] == need[s[left]]) {
                valid--;
            }  // 注意:先判断,再减
            window[s[left]]--;
            left++;  // 注意:左边届的收缩,要写在所有处理完成的最后
        }
        // 采集答案...
        right++;  // 注意:右边届的扩展,要写在所有处理完成的最后
    }
}


目前大多数的滑动窗口都可以用这个模版

注意:可能会在各个地方去采集结果,一般会放在左窗口收缩后,因为此时窗口是满足题目要求的,但要「随机应变」

本题解法:

思路:指定移动步长+指定开始位置
滑动窗口模版题,一一把题目中的要求实现即可,目前做过的滑窗题都可以套模版

这道题和平常的滑窗题有些不同,但其实都一个套路

最重要的条件「长度相同」,暗示滑动窗口移动的步长 stride 为 words[0].size()

窗口的长度应为 words.size() * stride

当前窗口的长度计算公式为 right - left + stride

设定滑动窗口的起始位置,起始位置可选范围为 [0, stride-1]
时间复杂度:O(30 * n * 30) 遍历起始位置最大 30 次,滑动窗口遍历整个 s 为 n 次,滑窗内 substr 的截取和判断为最大 30 次

小知识

from collections import Counter
Counter()
主要功能:可以支持方便、快速的计数,
将元素数量统计,然后计数并返回一个字典,键为元素,值为元素个数。
class Solution:
    def findSubstring(self, s, words):
        # 遍历串长度
        n =len(s)
        # 步长 words 中单词长度一致,任意都可
        stride=len(words[0])
        # 限制条件 (总的目标串长度)
        limit=len(words)*stride
        # 将字串存入字典
        need=collections.defaultdict(int) # 一种默认设定字典的方式 int 表示全为0
        for x in words:
            need[x]+=1
        # 打印need结果为 defaultdict(<class 'int'>, {'foo': 1, 'bar': 1})
        ans=[]
        for start in range(0,stride):
            left=start
            right=start
            # 记录满足条件数量
            count=0
            # 窗口初始化
            window=collections.defaultdict(int)
            # 窗口移动   与双指针类似
            while right<n:
                # 右边界进入
                cur_right =s[right:right+stride]
                # 判断子字符是否满足
                if cur_right in need:
                    window[cur_right]+=1
                    # 判断是否已经找到满足条件的子字串,计数
                    if window[cur_right]==need[cur_right]:
                        count+=1
                # 左边界收缩
                if right-left+stride>limit:
                    cur_left=s[left:left+stride]
                    if cur_left in need:
                        if window[cur_left]==need[cur_left]:
                            count-=1
                        window[cur_left]-=1
                    # 窗口滑动
                    left+=stride
                # 采集答案
                if right - left + stride == limit and cnt == len(need):
                    ans.append(left)

                right += stride

        return ans

欢迎大家提问,给出建议。

  • 11
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

01_

感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值