单调队列学习笔记:滑动窗口最大值,绝对差不超过限制的最长连续子数组

学习路线参考:单调队列 滑动窗口最大值【基础算法精讲 27】_哔哩哔哩_bilibili

ps:笔记和代码按本人理解整理,重思路

【如果笔记对你有帮助,欢迎关注&点赞&收藏,收到正反馈会加快更新!谢谢支持!】

上期笔记:

单调栈学习笔记(一):每日温度,接雨水-CSDN博客

题目1:滑动窗口最大值

239. 滑动窗口最大值 - 力扣(LeetCode)

  • “单调队列+滑动窗口”常同时出现,因为滑动窗口遵循“先来先走”,单调队列可以知道最大/小值
  • 本题维持一个单调递减的队列(即左边为最大值)
    • 队列左边的值一定比右边的值先来(“先来先走”)
    • 队列从左(大)到右(小)单调递减 :
      • 比如当前窗口为 [3 4 2] x x ,维持的队列对应的值为 [4 2] 
      • 没有 3 因为不可能是当前的最大值更不会是后面的最大值
      • 保存 2 因为窗口向后移动,2 可能是后面窗口 [2 x x]的最大值
      • 又比如当前窗口为 [3 4 5] x x ,维持的队列对应的值为 [5]
  • 例子
    单调队列学习笔记:滑动窗口最大值,绝对差不超过限制的最长连续子数组_第1张图片
  • 如何实现:右边的数入队列 → 左边的数出队列 → 记录当前滑动窗口结果
    • 用 for 循环遍历 nums,移动右界
    • 右边的数入队列:因为要维持单调队列,如果“新来的数 < 队列最后一个”,直接加入;如果“新来的数 >= 队列最后一个”,去掉队列最后一个,直到满足 “队列为空” 或 “新来的数 < 队列最后一个” 【类似单调栈,可以看上期笔记】
    • 左边的数出队列:满足滑动窗口的移动,需要查看当前长度是否超过k,因为一开始右界移动时长度 <=k
    • 记录当前滑动窗口结果:长度到达 k 时,记录结果(除了右界一开始移动的时候,后面都要记录)
  • 代码:
    class Solution:
        def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
            result = []  # 记录结果
            queue = deque()  # 初始化队列
            for idx, num in enumerate(nums):  # 滑动窗口向右移动
                # 入队列(维持单调队列)
                while queue and nums[queue[-1]] < num:
                    queue.pop()
                queue.append(idx)
                
                # 维持滑动窗口,如果滑窗右移之后长度超了,窗口左边也要向右移动(表现为单调队列先来先走)
                if idx - queue[0] >= k:
                    q.popleft()
    
                # 构成第一个长度为k的滑动窗口开始,记录结果
                if i >= k-1:
                    result.append(nums[queue[0]])
    
            return result
    

题目2:绝对差不超过限制的最长连续子数组

1438. 绝对差不超过限制的最长连续子数组 - 力扣(LeetCode)

  • 这也是道 “单调队列+滑动窗口” 题,“滑动窗口”框出来绝对差不超过限制的数,“单调队列”记录当前窗口的最大&最小值,以便求绝对差
  • 需要一个单调递增的队列【记录最小值】和一个单调递减的队列【记录最大值】
  • 如何实现:右边的数入队列 → 左边的数出队列 → 记录当前滑动窗口结果
    • 用 for 循环遍历 nums,移动右界
    • 右边的数入队列:加入两个队列,同时保持两个队列的单调(和上题一样)
    • 左边的数出队列:如果绝对差大于limit,说明要缩小滑动窗口(左端右移)
    • 记录当前滑动窗口结果:记录当前窗口长度
  • 代码:
    class Solution:
        def longestSubarray(self, nums: List[int], limit: int) -> int:
            result = 0
            left = 0  # 滑动窗口的左端
            queue_max = deque()  # 存窗口中的最大值
            queue_min = deque()  # 存窗口中的最小值
            for idx, num in enumerate(nums):  # 滑动窗口右端右移
                # 新来的数要同时装到两单调队列中
                while queue_min and num < nums[queue_min[-1]]:
                    queue_min.pop()
                while queue_max and num > nums[queue_max[-1]]:
                    queue_max.pop()
                queue_min.append(idx)
                queue_max.append(idx)
    
                # 看绝对差是否超过限制,如果超过,滑动窗口要缩小(左端右移)
                while nums[queue_max[0]] - nums[queue_min[0]] > limit:
                    # 移掉“先来”的数(索引值小的,即靠左边的)
                    if queue_max[0] < queue_min[0]:
                        left = queue_max[0] + 1
                        queue_max.popleft()
                    else:
                        left = queue_min[0] + 1
                        queue_min.popleft()
                           
                result = max(result, idx-left+1)  # 记录当前窗口长度
    
            return result

你可能感兴趣的:(leetcode,学习,笔记,单调队列,leetcode,力扣,算法)