滑动窗口——最小覆盖子串

一.题目描述

76. 最小覆盖子串 - 力扣(LeetCode)

二.题目解析

题目还是很好理解的,就是在字符串s中找到一个子串,该子串包含字符串t的所有字符。返回最短的子串。如果s中不包含这样的子串就返回一个空串。

需要注意的是:如果字符串t包含重复字符,比如"aaac",那么在s中寻找子串的时候必须包含所有的字符,不是说同样的字符出现一次就行了。还有就是在子串中的顺序可以随机,只要子串包含所有的t字符就行了。

三.算法原理

1.暴力解法

暴力解法还是很简单的,我们只需要暴力枚举子串,在枚举的过程中,用哈希表统计子串中的字符,当哈希表中出现了字符串t的所有字符之后,就记录此时子串的起始位置和长度。然后从下一个位置继续重新开始枚举,重复该过程,找到符合要求的最短子串即可。

 时间复杂度:在枚举子串的过程中,需要反复遍历s,所以时间复杂度为O(N^2)

空间复杂度:在该过程中,需要用到哈希表来存储数据,但是存储的都是字母,所以空间可以算是常数级的,空间复杂度为O(1).

2.滑动窗口

我们可以对上面的暴力枚举进行一些优化来提高时间复杂度。为了不失一般性,我们这里采取抽象的数组来表示优化的过程:

优化:

        当一个子串满足要求之后,left向前走的时候,有可能有两种情况:子串依旧满足要求或者不满足要求。

        满足要求:此时区间一定比上一个区间短,所以更新结果,left继续移动

        不满足要求:此时让right移动。但是right没有必要回到left的位置重新开始遍历

从优化方案来看,left和right指针都在向同一个方向运动,所以此时可以用滑动窗口来解决。

进窗口:right位置的元素放入哈希表中

判断:如果子串满足要求,就更新结果,然后让left位置的元素出窗口。继续判断,重复该过程。

时间复杂度分析:left和right在最坏情况下会分别遍历一次字符串,2n;但是判断的时候需要遍历哈希表进行比较,这也是有消耗的,记作t,则整体上时间复杂度为O(N+t)。

空间复杂度分析:字符集只是52个字母,所以我们可以认为是常数级的大小,空间复杂度为O(1). 

3.对滑动窗口进行优化 

每枚举一个区间,就要判断一次哈希表对应元素是否相等,很耗时间。我们可以使用一个count计数器来记录当前区间的有效字符的种类来对判断逻辑进行优化。之前在题目:找到字符串中所有的字母异位词就用到了count来优化,只不过哪里是用count标记的是有效字符的个数。

优化:

        我们在入窗口之后,判断该元素在两个哈希表的个数是否相等,如果相等,则说明这个字符已经满足了t中这个字符的出现要求,可以算作是有效字符了。count++

        在出窗口之前,我们对要出的元素进行判断,如果两个哈希表中该字符个数相等,说明该字符是一个有效的字符种类,出去之后,有效字符的种类就要减少,count--。

        判断的逻辑:因为count是统计有效字符的种类,所以他要和哈希表t的size进行比较(哈希表t的size表明了t中有几种字符。)

四.代码实现

在进行滑动窗口之前,我们要记录哈希表mp_t的size,因为在滑动窗口过程中,mp_t会插入元素,导致size变化,所以我们要提前记录一下。

因为我们要返回的是子串,所以我们要有子串的起始位置,以及长度。注意一下这里更新len和beg的逻辑。

string ret;
int n = s.size();
int m = t.size();
if (n < m)
    return ret;

unordered_map<char, int> mp_t; // 统计t字符串中字符的出现次数
unordered_map<char, int> mp_s; // 统计窗口中字符的出现次数
for (auto e : t) mp_t[e]++;
int judge = mp_t.size();

int len = 0, beg = 0;
for (int l = 0, r = 0, count = 0; r < n; ++r) // count标记有效字符的种类
{
    // 进窗口 + 维护count
    mp_s[s[r]]++;
    if (mp_s[s[r]] == mp_t[s[r]]) count++;

    // 判断
    while (count == judge)
    {
        // 更新结果
        int prev = len;
        len = len == 0 ? (r - l + 1) : min(len, r - l + 1);
        if (len != prev) beg = l;

        // 出窗口
        if (mp_s[s[l]] == mp_t[s[l]]) count--;
        mp_s[s[l++]]--;
    }
}
return s.substr(beg, len);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值