双指针之滑动窗口

滑动窗口的双指针用法一般用于解决子串问题,东哥的从我写了套框架,把滑动窗口算法变成了默写题中提到了相应的模板,我这里写一下python版本的:

def slidingWindow(s, t):
    need = collections.defaultdict(int)
    window = collections.defaultdict(int)
    left, right, valid = 0, 0, 0
    
    while right < len(s):
        # c是要移入窗口的字符
        c = s[right]
        # 窗口右移
        right += 1
        # 窗口内数据更新
        
        # debug位置
        print("l,r:", left, right)
        
        # 判断左侧窗口缩小的时机
        while (window needs shrink):
            # d是要移出去的元素
            d = s[left]
            # 窗口左边界右移,缩小
            left += 1
            # 数据更新逻辑
            
    return XX

然后咱来看一道题:

76. 最小覆盖子串

image-20211022113141572

这道题的主要思路详细的解说可以参加上面东哥的文章,我这里不再赘述。大概思路就是:先拓展窗口右边界使之找到可行解,在缩小左边界直至找不到解,在这之前最后的结果就是最优解,判断子串是否存在需要用到额外的map来进行记录对比,还需要用到Valid标志来监督是否处于有解状态:

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        need = collections.defaultdict(int)
        window = collections.defaultdict(int)
        # 构建目标子串的map,k为字符,v为个数
        for c in t: need[c] += 1
        # 两个指针都初始化到最左边,valid是满足need中k-v对的数量。
        left, right, valid = 0, 0, 0
        # 初始化
        start, length = 0, float('INF')

        while right < len(s):
            # 取出窗口中最右边的一个字符,窗口右边滑动一位
            c = s[right]
            right += 1

            # 如果当前的右边界的字符c属于目标子串t,记录命中的window_map对应元素value加一,如果某个K全部,valid计数加一
            if need.get(c):
                # 注意先加进来再判断
                window[c] += 1
                if window[c] == need[c]:
                    valid += 1
        
            # 对应的字串在当前窗口中满足,判断左侧边界是否需要收缩,注意判断条件是len(need)
            while valid == len(need):
                # 更新最小覆盖子串
                if right - left < length:
                    start = left
                    length = right - left
                # d是要移出窗口的字符,移除d直到valid == len(need)无法满足
                d = s[left]
                left += 1
                if need.get(d):
                    if window[d] == need[d]:
                        valid -= 1
                    # 注意先判断再移除
                    window[d] -= 1
        if length == float("INF"):
            return ""
        else:
            return s[start:start+length]

再看一题看看:

567. 字符串的排列

image-20211022150904951

本题和上一题很像,只需要注意左边界缩小的条件以及返回的时候的判断条件即可:

class Solution:
    def checkInclusion(self, s1: str, s2: str) -> bool:
        need = collections.defaultdict(int)
        window = collections.defaultdict(int)
        for k in s1: need[k] += 1

        left, right, valid = 0, 0, 0

        while right < len(s2):
            c = s2[right]
            right += 1

            if need.get(c):
                window[c] += 1
                if need[c] == window[c]:
                    valid += 1
            # 注意此处的条件,是窗口长度大于等于子串长度
            while right - left >= len(s1):
                # 注意是len(need),不是len(s1),因为valid记录的是k-v都满足的个数
                if valid == len(need):
                    return True
                d = s2[left]
                left += 1
                
                if need.get(d):
                    if need[d] == window[d]:
                        valid -= 1
                    window[d] -= 1
        return False

继续:

438. 找到字符串中所有字母异位词

image-20211022151152705

在完全没想怎么做的情况下默写了下来:

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        need = collections.defaultdict(int)
        window = collections.defaultdict(int)
        for k in p: need[k] += 1

        left, right, valid = 0, 0, 0
        res = []

        while right < len(s):
            c = s[right]
            right += 1

            if need.get(c):
                window[c] += 1
                if need[c] == window[c]:
                    valid += 1
            # 由于还是需要长度相等,所以这里还是判断窗口是不是大于目标子串长度
            while right-left >= len(p):
                if valid == len(need):
                    # 找到不返回记录结果
                    res.append(left)
                d = s[left]
                left += 1

                if need.get(d):
                    if need[d] == window[d]:
                        valid -= 1
                    window[d] -= 1
        return res

再来一道:

3. 无重复字符的最长子串

image-20211022152529973

这题稍微有一点点不一样,但是用文章最开头的套路还是能解的,需要理解这个窗口扩大缩小的时机以及题目要求返回的东西,看一下代码:

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        window = collections.defaultdict(int)
        
        left, right = 0, 0
        res = 0
        
        while right < len(s):
            c = s[right]
            right += 1
            # 窗口扩充
            window[c] += 1
            # 出现重复的就开始缩小窗口
            while window[c] > 1:
                d = s[left]
                left += 1
                # 窗口缩小,此时肯定是不包含重复元素的,所以当前窗口长度就是新的一个答案
                window[d] -= 1
            # 每次稳定窗口后都需要更新一下最大长度
            res = max(res, right - left)
        return res

至此,双指针的三种常规用法我们都看完了,需要时常拿出来复习哦。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值