算法练习题09:滑动窗口最大值(队列、双端队列)

class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return new int[0];
        }

        int n = nums.length;
        int[] result = new int[n - k + 1];
        Deque<Integer> deque = new LinkedList<>();

        for (int i = 0; i < n; i++) {
            // 移除不在窗口内的元素(即索引小于 i-k+1 的元素)
            if (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
                deque.pollFirst();
            }

            // 移除所有比当前元素小的元素,它们不可能是最大值
            while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }

            // 将当前元素的索引加入到队列中
            deque.offerLast(i);

            // 当前索引从 i >= k-1 时,开始记录最大值
            if (i >= k - 1) {
                result[i - k + 1] = nums[deque.peekFirst()];
            }
        }

        return result;
    }
}

代码分步解释

  1. 初始化与边界检查

    首先,检查输入的数组是否为空或长度为零。如果是这样,直接返回一个空数组,因为没有任何滑动窗口可计算最大值。
  2. 创建结果数组与双端队列

    确定数组的长度,并创建一个结果数组 result 来存储每个滑动窗口的最大值。结果数组的长度为 n - k + 1,因为这是滑动窗口在数组中滑动的次数。创建一个双端队列(Deque),用于存储滑动窗口中可能的最大值的元素的索引。双端队列允许我们在常数时间内从两端插入或删除元素。
  3. 遍历数组

    开始遍历数组的每一个元素。对于每个元素,我们将更新双端队列,确保队列中的元素始终是当前窗口中可能的最大值。
  4. 移除窗口外的元素

    检查双端队列的最前端(即第一个元素),如果它的索引已经超出了当前滑动窗口的左边界(即索引小于 i - k + 1),就将它从队列中移除。这是因为该元素已经不再属于当前窗口,不可能再成为最大值。
  5. 保持队列的有序性

    为了确保队列中的元素按从大到小的顺序排列,我们会移除队列中所有比当前元素小的元素。因为如果当前元素比这些元素大,那么这些元素在当前窗口内或未来的窗口中都不可能成为最大值。
  6. 加入当前元素

    将当前元素的索引加入到双端队列的末尾。这个步骤保证了双端队列始终包含当前窗口内可能的最大值候选者。
  7. 记录当前窗口的最大值

    当窗口的大小达到 k 时,开始记录每个窗口的最大值。双端队列的第一个元素就是当前窗口的最大值,因为队列中的元素已经被排序,最大值在最前端。
  8. 返回结果

    完成数组遍历后,返回结果数组,其中包含了每个滑动窗口的最大值。

双端队列(Deque)基础知识

什么是双端队列?

双端队列(Deque,全称为Double-Ended Queue)是一种特殊类型的队列,它允许在队列的两端进行插入和删除操作。与普通的队列不同,双端队列在队头和队尾都可以进行元素的添加和移除操作。

双端队列的基本操作:
  1. offerFirst(E e) 和 offerLast(E e):在队列的前端或后端插入元素。
  2. pollFirst() 和 pollLast():从队列的前端或后端移除并返回元素。
  3. peekFirst() 和 peekLast():查看队列的前端或后端元素,但不移除它。
为什么在滑动窗口问题中使用双端队列?

       1. 高效的最大值维护:双端队列允许我们高效地追踪当前滑动窗口的最大值。我们可以在 O(1) 的时间复杂度内添加、移除元素,并快速确定窗口的最大值。

        2.动态管理窗口内的元素:双端队列帮助我们在滑动窗口移动时动态管理元素,使得队列中的元素始终是当前窗口内可能的最大值候选者。

在滑动窗口问题中的应用:
  1. 维护一个有序的候选最大值队列:在每次滑动窗口时,我们用双端队列来存储窗口内可能的最大值,并保证这些元素的顺序。
  2. 高效移除过期元素:当滑动窗口向右移动时,如果队列中最前端的元素已经不在当前窗口范围内,我们可以快速将其移除。
  3. 保持窗口内的元素降序排列:每当我们处理一个新元素时,如果发现它比队列中的一些元素大,我们就移除这些较小的元素,因为它们不再可能成为最大值。

操作示例

Deque<Integer> deque = new LinkedList<>();

// 在队列的头部添加元素
deque.offerFirst(1);

// 在队列的尾部添加元素
deque.offerLast(2);

// 查看队列的头部元素
int firstElement = deque.peekFirst();  // 返回 1

// 查看队列的尾部元素
int lastElement = deque.peekLast();  // 返回 2

// 从队列的头部移除元素
int removedFirst = deque.pollFirst();  // 返回 1,队列现在是 [2]

// 从队列的尾部移除元素
int removedLast = deque.pollLast();  // 返回 2,队列现在为空

基础队列(Queue)基础知识

什么是队列?

队列(Queue)是一种先进先出(FIFO,First-In-First-Out)的数据结构。这意味着第一个进入队列的元素将是第一个被移除的元素。可以把它想象成排队的情形,最先排队的人最先得到服务。

队列的基本操作:
  1. offer(E e) 或 add(E e):在队列的尾部添加一个元素。这相当于一个人排队进入队列。
  2. poll() 或 remove():从队列的头部移除并返回第一个元素。这相当于最先排队的人被服务后离开队列。
  3. peek() 或 element():查看队列头部的第一个元素,但不移除它。这相当于查看排在最前面的人是谁,但不让他离开队列。
队列的应用场景:

        1.任务调度:在操作系统中,任务排队等待 CPU 处理。

        2.广度优先搜索(BFS):在图或树的遍历中,队列用于按层级顺序访问节点。

        3.生产者-消费者模式:在多线程编程中,队列用于在生产者和消费者之间传递数据。

操作示例

Queue<Integer> queue = new LinkedList<>();

// 添加元素到队列的尾部
queue.offer(1);
queue.offer(2);
queue.offer(3);

// 查看队列的头部元素,但不移除
int head = queue.peek();  // 返回 1

// 移除并返回队列的头部元素
int removedElement = queue.poll();  // 返回 1,队列现在是 [2, 3]
  • 14
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值