滑动窗口|最小覆盖子串&字符串的排列&无重复最长子串

 

 

滑动窗口算法的思路是这样:

  • 1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引左闭右开区间 [left, right) 称为一个「窗口」。

  • 2、我们先不断地增加 right 指针扩大窗口 [left, right),直到窗口中的字符串符合要求(包含了 T 中的所有字符)。

  • 3、此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right),直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。

  • 4、重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。

 

使用cntMap优化子串判断条件,统计每个子串需要出现的次数,以及需要几个不同的字母nums

 

时间复杂度:O(m+n),m,n分别为字符串m和n的长度

空间复杂度:O(n)

复制代码
func getSubStr(s, t string) string {
    cntMap := make(map[byte]int, len(t))
    nums := 0
    for _, c := range []byte(t) {
        if cntMap[c] == 0 {
            nums++ // 统计t字符串中有几种字母出现
        }
        cntMap[c]++ //记录t字符串中每个字母需要出现的次数
    }

    ans1, ans2 := -1, len(s) //初始化最优解的左右窗口下标
    left := 0                //从[0,0]区间开始,右指针不断右移
    for right, c := range []byte(s) {
        cntMap[c]--         // 对应次数减1
        if cntMap[c] == 0 { //该字母满足出现次数要求
            nums-- //子串需要的字母总数减少一个
        }
        for nums == 0 { // 开始找最优解,直到子串字母需要出现的次数都满足,
            if right-left < ans2-ans1 { // 找到更短的子串
                ans1, ans2 = left, right // 记录此时的左右端点
            }
            x := s[left]        // 左端点字母
            if cntMap[x] == 0 { // x 移出窗口之前,检查出现次数,x字符移走后,次数要+1
                nums++
            }
            cntMap[x]++ // 左端点字母移出子串
            left++
        }
    }
    //左指针没有移动过,最大化窗口也不满足条件,没有找到
    if ans1 < 0 {
        return ""
    }
    return s[ans1 : ans2+1]
}
复制代码

 

 

 

题意:该题等价于,s1的每种字符的出现次数,与 s2 某个子串的每种字符的出现次数相同。假设该子串的位置为[start,end],使用滑动窗口找到该区间,并且该区间内字符出现次数相同即可

题解:使用两个数组记录两个子串每个字符出现的次数,这个次数作为辅助使用,用于快速判断是否存在次数相对的子串。使用滑动窗口匹配出现次数

 

 

复制代码
func checkInclusion(s1 string, s2 string) bool {
    count1, count2 := [26]int{}, [26]int{} // 两个长度为26的数组

    for i := 0; i < len(s1); i++ { // 统计s1的字符的出现次数
        count1[s1[i]-'a']++
    }
    start := 0         // 指向s2子串的开头

    for i, c := range s2 { // 遍历s2的字符
        count2[c-'a']++      // 将s2的字符统计到count2

        // 如果start上的字符,count2的比count1多,这说明start不是合格子串的开头,start应该步进,舍弃这个字符
        for start <= i && count1[s2[start]-'a'] < count2[s2[start]-'a'] {
            count2[s2[start]-'a']--
            start++
        }
        // 如果两个count数组全等,说明找到了满足条件的子串
        if count1 == count2 { // go里面数组是基本类型,可以这么比较
            return true
        }
    }

    // 考察了所有s2的字符,都没有找到合适的子串
    return false
}
复制代码

题解:https://leetcode.cn/problems/permutation-in-string/solutions/599506/deng-jie-zhuan-hua-wen-ti-hua-dong-chuan-u916/

 

 

最长无重复子串

 

题解:

从头开始遍历字符串,对每一个遍历过的字符判断是否被标记过,若标记过,则更新左边界le=vis[temp]+1,同时更新当前不重复子串的长度;否则用数组下标进行标记,不断右移右边界ri,更新最大长度

 

时间复杂度:O(n)

空间复杂度:O(n)

复制代码
func lengthOfLongestSubstring(s string) int {
    le, ri, maxLen, ans := 0, 0, 0, 0
    vis := make([]int, 256)

    for i := range vis {
        vis[i] = -1
    }
    
    for ri < len(s) {
        temp := s[ri]
        if vis[temp] >= le {
            le = vis[temp] + 1
            maxLen = ri - le
        }
        vis[temp] = ri
        ri++
        maxLen++
        if ans < maxLen {
            ans = maxLen
        }
    }
    return ans
}
复制代码

 

 

 

 

 

 

 

 

题解:使用滑动窗口,不断调整右指针,直到满足字符数量,然后在调整左指针找最优解

复制代码
func findAnagrams(s, p string) (ans []int) {
    cnt := [26]int{} // 统计 p 的每种字母的出现次数
    for _, c := range p {
        cnt[c-'a']++
    }
    left := 0
    for right, c := range s {
        c -= 'a'
        cnt[c]-- // 右端点字母进入窗口
        for cnt[c] < 0 { // 字母 c 太多了
            cnt[s[left]-'a']++ // 左端点字母离开窗口
            left++
        }
        if right-left+1 == len(p) { // s' 和 p 的每种字母的出现次数都相同
            ans = append(ans, left) // s' 左端点下标加入答案
        }
    }
    return
}
复制代码

复杂度分析

  • 时间复杂度:O(m),其中 m 是 s 的长度
  • 空间复杂福:O(n)

https://leetcode.cn/problems/find-all-anagrams-in-a-string/solutions/2969498/liang-chong-fang-fa-ding-chang-hua-chuan-14pd/

posted @   知道了呀~  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
历史上的今天:
2019-12-23 1003 Emergency (25分) 求最短路径的数量
点击右上角即可分享
微信分享提示