leetcode刷题笔记-数组-滑动窗口

一、滑动窗口的常见问题分析

  • 问题

    给定一个含有 n 个正整数的数组和一个正整数 target 。

    找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

    209. 长度最小的子数组

  • 问题分析 (滑动窗口问题的解题思路)

    • 当看到题目要求==在数组中找到符合某一条件的连续子数组==时,优先想到使用滑动窗口!!!
    • 使用滑动窗口分析问题,弄清楚以下步骤即可
      • 首先,弄清楚窗口内的元素需要符合什么条件,当未达到条件时,让窗口一直扩张
      • 当窗口内元素符合条件时,就可以获取窗口的大小,同时开始从窗口左侧缩小窗口,从而开始下一个窗口的查找。(难点就在于窗口左侧应该如何放置
    • 找到所有符合条件的窗口,然后比较得到符合题意的窗口,返回即可。
  • 本题解决方案

    • 窗口内的元素需要满足的条件为:窗口内的元素和>=target
    • 当窗口内元素符合条件后,窗口左侧只需要往右移动,直到窗口内元素不满足条件为止即可
    func minSubArrayLen(target int, nums []int) int{
        left := 0
        sum := 0 	//用于计算窗口内的元素和
        min := len(nums)+1
        for j:=0;j<len(nums);j++{ //j为窗口右边界,当条件为满足时,窗口一直扩张
            sum += nums[j]
            for sum >= 7{    // 找到窗口内元素需要满足的条件
                if min > j - left + 1{  // 获取窗口长度
                    min = j - left + 1
                }
                sum -= nums[left]    // 缩小窗口
                left++ 
            }
        }
        if min > len(nums){ // 如果没找到符合条件的窗口
            return 0
        }
        return min
    }
    

二.进阶问题:限定窗口内的元素种类问题

2.1 水果成篮问题

  • 问题:904. 水果成篮

  • 问题分析

    • 这道题从题目来看,在一数组中找符合条件的最大子数组,显然符合使用滑动窗口法的条件
    • 首先确定窗口内元素的条件,窗口内的元素,要满足只有两种类别,即相同的数字只能有两类
    • 当第三类元素出现在窗口内部时,就可以获取窗口长度,然后考虑窗口左侧边界的位置了。
      • 当窗口内出现第三个元素时,左侧边界移动到什么位置呢,当然是要移动到窗口内仅有两个种类的时候了,因此我们是不是应该用一个数据结构来记录窗口内每种元素的个数,当移动窗口左边界时,让对应种类的个数减一,一直到某个种类的元素个数为0,就可以令下一个位置为窗口的新左边界了
      • 每个种类对应一个数量,典型的key-value键值对类型,因此可以使用hashMap结构
  • 解决方案

    func totalFruit(fruits []int) int{
        left := 0
        max := -1
        kind := make(map[int]int)
        
        for right:=0;right<len(fruits);right++{
            kind[fruits[right]]++ // 记录每个种类的水果个数
            
            for len(kind) > 2{ // 如果篮子里大于2种水果了
                if max < right-left {  // 获取窗口长度
                    max = right - left
                }
                
                // 调整窗口左边界位置
                kind[fruits[left]]--  
                if kind[fruits[left]] == 0{  // 当某种水果个数为0时,从篮子种删除该水果
                    delete(kind,fruits[left])
                }
                left++
            }
        }
        
        if max < right -left{ // 如果一直到数组尾端都符合条件,则循环中不会进入判断,因此在末尾还要判断一次
            max = right-left
        }
        
        return max
    }
    

2.2 最小覆盖字串问题

  • 问题:76. 最小覆盖子串

  • 问题分析

    • 字符串可以看成特殊的数组,该题寻找最小子串,显然也可以使用滑动窗口法
    • 这题与上面一题有着相同点,那就是其窗口内的元素要满足一定的种类,此外,本题还要满足每种种类的元素个数
    • 首先,我们可以定义一个hashMap(mapT)来记录t串的字符种类与每种字符的个数
    • 其次,我们可以再定义一个hashMap(mapS)来记录窗口内已经满足条件的字符种类,类似于上一题的篮子
    • 接下来就是滑动窗口法解决问题
      • 窗口内的元素应该满足的条件为,len(mapS)==len(mapT),即篮子里要包含t串的所有字符
      • 找到窗口后,获取窗口元素的长度,随后调整左边界,调整方法去上一题类似
  • 解决方案

    func minWindow(s string, t string) string{
        left,right := 0,0
        min := len(s)+1
        mapT := make(map[byte]int)  // 记录t串的字符种类及个数
        mapS := make(map[byte]int) // 将满足条件的字符放入篮子
        result := ""
        
        for _,v := range t{ // 记录t串的字符种类及个数
            mapT[byte(v)]++
        }
        
        for  ;right < len(s);right++{
            if _,ok := mapT[s[right]];ok{   // 判断是否是t串中有的字母,如果不是直接跳过
                mapT[s[right]]-- 
                if mapT[s[right]] <= 0{  // 判断个数是否满足,如果个数也满足了,说明该字符已经符合条件,放入篮子
                    mapS[s[right]] = right  
                }
            }
            
            for len(mapS) == len(mapT){  //当窗口内容符合条件
                if min > right - left + 1{ // 比较获取最小子串
                    min = right - left + 1
                    result = s[left:right+1] // 左闭右开区间
                }
                
                // 移动左边界,即将其中一个满足条件的字符从篮子里剔除,谁先满足就剔除谁
                if _,ok := mapT[s[left]];ok{ //与t串字符相匹配,则数量加1
                    mapT[s[left]]++
                }
                
                if mapT[s[left]] > 0{ // 当数量大于0,说明此时篮子里的该字符个数已不满足t串,删除该字符
                    delete(mapS,s[left])
                }
                left++
            }
        }
        
        if min < right - left + 1{ // 如果最后都满足窗口,再判断一次
            min = right - left + 1
            result = s[left:right+1]
        }
        return result
    }
    

三、寻找字符串中所有字母的异位词

  • 问题:438. 找到字符串中所有字母异位词 - 力扣(Leetcode)

  • 分析:

    • 首先,这道题是要在一个字符串中找满足条件的连续子串,满足滑动窗口法的前提
    • 那么这里的窗口的条件是什么呢?
      • 一个是窗口内的字母必须是p包含的字母
      • 二是窗口的长度是固定的,即p的长度
    • 我们可以使用一个hash结构来记录p的字母,然后调节窗口右边界扩张窗口,一旦有某个字母不符合条件了,说明当前窗口一定是不符合条件的,因此我们要调节窗口的左边界,把不符合条件的元素排除出去,保证我们窗口内的元素一定是满足条件的
    • 当窗口长度变为p的长度时,我们就可以通过左边界获取窗口的起始索引了
  • 解决方案

    //ps:该解题代码是参考leetcode题解下@Edward Elric用户的评论而写的,我认为这位大佬这道题的题解比官方的简洁的多,respect!
    func findAnagrams(s string,p string)[]int{
        cnt := [26]int{}
        left,right := 0,0
        ans := make([]int,0)
        
        for _,ch := range p{ //记录p
            cnt[ch-'a']++
        }
        
        for right < len(s){
            if cnt[s[right]-'a'] > 0{ // 满足条件1
                cnt[s[right]-'a'] --
                right++
                if right - left == len(p){
                    ans = append(ans,left)
                }
            }else{ // 不满足条件1,立刻调整窗口,保证窗口内元素时刻满足条件1
                cnt[s[left]-'a']++
                left++
            }
        }
        return ans
    }
    
    • 这道题与前面两题的不同之处在于:
      • 前面两题我们是在窗口未满足条件时扩张窗口,满足条件后调整窗口,而本题则是在不满足条件时调整窗口。
      • 前面两题的窗口长度是未知的,而本题的窗口是定长的

总结

  • 遇到在数组种找一个==满足一定条件的连续子数组==时,考虑使用滑动窗口法。
  • 滑动窗口,优先找到窗口内元素需要满足的条件,以此作为判断的入口
  • 窗口内元素达到条件后,要想办法调整窗口左边界更新窗口,使得更新后的窗口不满足条件
  • 如果子数组要求的元素种类有限制,即所谓的篮子问题,可以借助hashMap作为篮子,以map的长度作为窗口条件。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值