双指针之滑动窗口

滑动窗口的双指针用法一般用于解决子串问题,东哥的从我写了套框架,把滑动窗口算法变成了默写题中提到了相应的模板,我这里写一下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

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

### 双指针滑动窗口算法的应用 双指针滑动窗口算法是一种高效的线性扫描方法,在处理数组或字符串问题时特别有用。该算法能够有效地解决涉及连续子序列或子串问题,而不需要嵌套循环带来的高时间复杂度。 #### 应用场景 1. **寻找特定条件下的最小子串** 当面对需要找出满足某些约束条件下最小长度的子串问题时,滑动窗口可以通过动态调整窗口大小来高效求解[^1]。 2. **计算不重复字符的最大数量** 对于像“无重复字符的最长子串”的题目,利用哈希表配合滑动窗口技术可以在一次遍历中完成查找工作[^2]。 3. **两数之和等于目标值** 如果是在已排序或者部分有序的数据结构上操作,则可以考虑使用对撞指针形式的双指针法;而在未排序情况下也可以先进行预处理再应用此策略。 4. **固定宽度内的极值查询** 需要频繁访问区间内元素特性(如最大/最小值)的情况下,借助单调队列优化后的滑窗能提供更优性能表现[^3]。 ### 实现示例 下面给出一段 Python 代码作为例子,用于解决问题:“给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。” ```python def lengthOfLongestSubstring(s: str) -> int: char_map = {} left, max_length = 0, 0 for right in range(len(s)): if s[right] in char_map and char_map[s[right]] >= left: left = char_map[s[right]] + 1 char_map[s[right]] = right current_window_size = right - left + 1 max_length = max(max_length, current_window_size) return max_length ``` 这段程序实现了上述提到的功能,并且采用了字典 `char_map` 来追踪已经遇到过的字符及其最新位置,以此决定何时应该收缩左侧边界以保持当前窗口内部不含重复项。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值