滑动窗口和双指针


预计阅读时间:5 分钟

滑动窗口和双指针

双指针的精髓在于「不回头」:每个元素只被指针扫过常数次,$O(n^2)$ 的枚举就塌缩成 $O(n)$。本合集收录滑动窗口与双指针题目。

分组循环

同向双指针的一种特殊形态:左端点 start 只在每组开头记一次,右端点就是循环变量 i 本身,组与组之间不回退。

适用场景

数组会被切成若干组,每一组的处理逻辑相同。

核心思想

外层循环负责进组前的准备(记录起点)和出组后的统计(更新答案);内层循环负责走完这一组,找出它最远在哪结束。

模板

n = len(nums)
i = 0
while i < n:
    start = i
    i += 1  # a group has at least one element, so i always advances
    while i < n and ...:  # usually compares nums[i] with nums[i - 1]
        i += 1
    # nums[start..i-1] is one group
    # the next group starts at i, no extra i += 1 needed

进内层循环前先 i += 1:每组至少有一个元素,这样外层每一轮 i 至少前进一步。如果省掉这一步,内层条件在 i == start 时就不成立的话,i 不动,外层会死循环。下面两题的代码都是这个形状。

题目

leetcode 978

判断湍流性质要连看三个数:组能从 arr[i] 延伸到 arr[i+1],当且仅当 (arr[i] - arr[i-1]) 和 (arr[i+1] - arr[i]) 异号,即乘积小于 0。两点细节:

  • 相邻两组共用一个端点:一组在 arr[i] 处断开时,arr[i] 仍可以是下一组的起点,所以下一组从 i 开始,不跳过它;
  • 相等的相邻元素不能开组,遇到就单独前进一步。
class Solution:
    def maxTurbulenceSize(self, arr: List[int]) -> int:
        n = len(arr)
        ans = 1
        i = 0
        while i < n - 1:
            if arr[i] == arr[i + 1]:  # equal pair cannot start a group
                i += 1
                continue
            start = i
            i += 1
            while i < n - 1 and (arr[i] - arr[i - 1]) * (arr[i + 1] - arr[i]) < 0:
                i += 1
            # arr[start..i] is turbulent; the next group starts at i
            ans = max(ans, i - start + 1)
        return ans

leetcode 3255

题目要求窗口里的数连续且升序(每个比前一个大 1),只是严格上升不够:[1,3,5]、k = 3 的答案是 [-1]。先用分组循环把数组切成若干段,每段内相邻元素差恰好为 1。长度为 k 的窗口逐格滑动、互相重叠:完全落在某一段里的窗口,最大值就是它的最后一个元素;跨段的窗口答案都是 -1。所以在每段里,以段内第 k 个及之后元素结尾的窗口填上该元素,其余保持 -1。如果没有「连续上升」这个前提,滑动窗口求最大值就要上单调双端队列来维护了。

class Solution:
    def resultsArray(self, nums: List[int], k: int) -> List[int]:
        n = len(nums)
        ans = [-1] * (n - k + 1)
        i = 0
        while i < n:
            start = i
            i += 1
            while i < n and nums[i] == nums[i - 1] + 1:
                i += 1
            # nums[start..i-1] is a consecutive ascending run
            for end in range(start + k - 1, i):
                ans[end - k + 1] = nums[end]
        return ans

leetcode 467

题意很绕,需要仔细读。要找的子串里,每个字母都是前一个字母的下一个,z 后面接 a(za 也合法),难点在如何统计去重。关键观察:起始字母相同时,长的合法子串一定完全覆盖短的,以某字母开头的合法子串总数就等于以它开头的最长长度。怎么求「以它开头的最长长度」:用分组循环把字符串切成若干个最长段,段内相邻字母依次相连(z 接 a),段 [start, i-1] 里位置 j 开头的最长长度是 i - j。于是按 26 个起始字母分组,每组取最长值,累加即为答案。


未完待续,本合集随训练进度持续补完。


本文由 aboom 原创,转载请注明出处。

📖相关推荐