leetcode-76. 最小覆盖子串(滑动窗口)

简介: 时间复杂度:最坏情况下左右指针对 s 的每个元素各遍历一遍,哈希表中对 s 中的每个元素各插入、删除一次,对 t 中的元素各插入一次。每次检查是否可行会遍历整个 t 的哈希表,哈希表的大小与字符集的大小有关,设字符集大小为 CC,则渐进时间复杂度为 O(C⋅∣s∣+∣t∣)。

51b4ffab03f64ec6a5fb99c007c2d8b5.png


题目链接:https://leetcode.cn/problems/minimum-window-substring/


思路


方法:滑动窗口


1.我们用两个map存取,mpt存t字符串中的每个字符的个数,cnt用来存当前子串 s[l:r] 中每个字符个数。


2.用check()函数判断当前s[l:r]中是否可以覆盖p字符串。


3.滑动窗口一个用于「延伸」现有窗口的 r 指针,和一个用于「收缩」窗口的 l 指针。在任意时刻,只有一个指针运动,而另一个保持静止。l,r截取s中的子串,每次r++的时候用check()匹配,check成功匹配,收缩窗口移动 l 找到s[l:r]中最小覆盖p的子串长度,并把当前的l,r赋值给ansL和ansR。


4.可能s串中不存在p的子串,这个时候我们只要判断ansL是否被改变,ansL和ansR默认初始为-1。


代码示例


func minWindow(s string, t string) string {
    cnt, mpt := map[byte]int{}, map[byte]int{}
    //统计t中每个字符个数
    for i := range t{
        mpt[t[i]]++
    }
    //截取子串的范围
    ansL, ansR := -1, -1
  //判断当前s[l:r]中是否可以覆盖p字符串
    check := func() bool{
        for k, v := range mpt{
            if cnt[k] < v{
                return false
            }
        }
        return true
    }
    sLen := len(s)
    len := math.MaxInt32
    for l, r := 0, 0; r < sLen; r++ {
        if r < sLen && mpt[s[r]] > 0 {
            cnt[s[r]]++
        }
        //check匹配当前s[l:r]是否覆盖p
        for check() && l <= r {
          //对比最小子串长度
            if r - l + 1 < len {
                len = r - l + 1
                ansL, ansR = l, l + len
            }
            if _, ok := mpt[s[l]]; ok {
                cnt[s[l]] -= 1
            }
            l++
        }
    }
    if ansL == -1{
        return ""
    }
    return s[ansL:ansR]
}

5235f14c421842ef9a5d49d24a56478f.png


复杂度分析


  • 时间复杂度:最坏情况下左右指针对 s 的每个元素各遍历一遍,哈希表中对 s 中的每个元素各插入、删除一次,对 t 中的元素各插入一次。每次检查是否可行会遍历整个 t 的哈希表,哈希表的大小与字符集的大小有关,设字符集大小为 CC,则渐进时间复杂度为 O(C⋅∣s∣+∣t∣)。


  • 空间复杂度:这里用了两张哈希表作为辅助空间,每张哈希表最多不会存放超过字符集大小的键值对,我们设字符集大小为 C ,则渐进空间复杂度为 O©。


代码优化


  • 在s[l:r]里面找到最小覆盖p字符串的子串,我们通过每次check来匹配是否可以覆盖,浪费了许多时间。


  • 我们可以改变思路,我们只要第一次通过check匹配是否存在p的字符串子串即可,我们移动l的位置,并且判断cnt[s[l]] - 1是否还能大于等于mpt[s[l]]的字符个数即可。


func minWindow(s string, t string) string {
    cnt, mpt := map[byte]int{}, map[byte]int{}
    //统计t中每个字符个数
    for i := range t{
        mpt[t[i]]++
    }
    //截取子串的范围
    ansL, ansR := -1, -1
  //判断当前s[l:r]中是否可以覆盖p字符串
    check := func() bool{
        for k, v := range mpt{
            if cnt[k] < v{
                return false
            }
        }
        return true
    }
    sLen := len(s)
    len := math.MaxInt32
    for l, r := 0, 0; r < sLen; r++ {
        if r < sLen && mpt[s[r]] > 0 {
            cnt[s[r]]++
        }
        //只需一次用check匹配当前s[l:r]是否覆盖p
        if check(){
            for l <= r {
                if r - l + 1 < len {
                    len = r - l + 1
                    ansL, ansR = l, l + len
                }
                if _, ok := mpt[s[l]]; ok {
                  //判断当前去掉一个s[l]是否还能匹配覆盖mpt[s[l]]的个数
                    if cnt[s[l]] - 1 < mpt[s[l]]{
                        break
                    }
                    cnt[s[l]] -= 1
                }
                l++
            }
        }   
    }
    if ansL == -1{
        return ""
    }
    return s[ansL:ansR]
}


caad0686c32d45e6b6f726d7caf07680.png


  • 时间复杂度:O(C⋅∣s∣) 字符集大小为 C
  • 空间复杂度:O© 字符集大小为 C
目录
相关文章
|
2天前
|
算法 搜索推荐
力扣每日一题 6/15 滑动窗口
力扣每日一题 6/15 滑动窗口
5 1
|
2天前
|
存储 算法 测试技术
力扣经典150题第三十三题:最小覆盖子串
力扣经典150题第三十三题:最小覆盖子串
6 1
|
17天前
|
算法
【LeetCode刷题】滑动窗口解决问题:串联所有单词的子串(困难)、最小覆盖子串(困难)
【LeetCode刷题】滑动窗口解决问题:串联所有单词的子串(困难)、最小覆盖子串(困难)
|
17天前
|
算法 容器
【LeetCode刷题】滑动窗口解决问题:水果成篮、找到字符串中所有字母异位词
【LeetCode刷题】滑动窗口解决问题:水果成篮、找到字符串中所有字母异位词
|
2天前
|
算法 索引
力扣随机一题 位运算/滑动窗口/数组
力扣随机一题 位运算/滑动窗口/数组
10 0
|
2天前
|
算法 测试技术 索引
力扣经典150题第三十二题:串联所有单词的子串
力扣经典150题第三十二题:串联所有单词的子串
5 0
|
17天前
【LeetCode刷题】滑动窗口思想解决:最大连续1的个数 III、将x减到0的最小操作数
【LeetCode刷题】滑动窗口思想解决:最大连续1的个数 III、将x减到0的最小操作数
|
17天前
【LeetCode刷题】滑动窗口思想解决问题:长度最小的子数组、无重复字符的最长子串
【LeetCode刷题】滑动窗口思想解决问题:长度最小的子数组、无重复字符的最长子串
|
17天前
|
算法
【经典LeetCode算法题目专栏分类】【第8期】滑动窗口:最小覆盖子串、字符串排列、找所有字母异位词、 最长无重复子串
【经典LeetCode算法题目专栏分类】【第8期】滑动窗口:最小覆盖子串、字符串排列、找所有字母异位词、 最长无重复子串
|
20天前
|
存储 算法 数据可视化
深入解析力扣159题:至多包含两个不同字符的最长子串(滑动窗口法详细图解)
深入解析力扣159题:至多包含两个不同字符的最长子串(滑动窗口法详细图解)