利用双端队列高效解决滑动窗口最大值问题---剑指offerJZ59 滑动窗口的最大值

本文介绍了如何使用双端队列(Deque)解决滑动窗口问题,通过实例展示了如何初始化、处理窗口和滑动过程,以及ArrayDeque在Java中的方法操作。重点在于队首和队尾的高效操作,以找到每滑动一次窗口内的最大值。
摘要由CSDN通过智能技术生成

目录

引言

本篇内容是在官方文档的基础上的个人解释旨在对双端队列进行解释

问题描述

双端队列(Deque)原理

解决方案

初始化

使用双端队列处理窗口

窗口滑动过程

实现代码

ArrayDequeue方法说明

队列首部(Front)的操作

队列尾部(Last)的操作

总结


引言

在数据结构和算法的世界里,面对不同的问题,选择合适的数据结构是至关重要的。本文通过解决一个具体的问题——寻找滑动窗口的最大值,来展示如何高效地使用双端队列(Deque)。我们将详细解释双端队列的工作原理,并展示其在解决这个问题时的应用。

题目链接:滑动窗口的最大值_牛客题霸_牛客网 (nowcoder.com)

本篇内容是在官方文档的基础上的个人解释旨在对双端队列进行解释

问题描述

给定一个长度为 n 的数组 num 和滑动窗口的大小 size,需要找出所有滑动窗口里数值的最大值。例如,对于数组 {2,3,4,2,6,2,5,1} 和滑动窗口大小为 3,我们的目标是找出每个窗口的最大值,即 {4,4,6,6,6,5}

双端队列(Deque)原理

在深入解决方案之前,首先理解双端队列是必要的。双端队列是一种具有队列和栈性质的抽象数据类型。它允许在队列的两端进行插入和删除操作,非常适合用于需要两端操作的场景。

From.Java ArrayDeque - Java教程 - 菜鸟教程 (cainiaojc.com)

从图中我们可以看出,双端队列允许在队列的前端和后端进行以下操作:

  • 添加元素(addFirstaddLast

  • 删除元素(removeFirstremoveLast

  • 访问元素(getFirstgetLast

在 ArrayDeque(双端队列)中,"队首"和"队尾"是相对的概念,取决于你如何使用队列。如果你选择从右侧(末尾)入队,那么在这种情况下,右侧就可以被视作队尾,左侧则是队首。

具体来说:
- 当你从右侧(末尾)使用 addLast 或 offerLast 方法向队列添加元素时,你实际上是在队尾添加元素。
- 相应地,左侧变成了队首。你可以使用 removeFirst 或 pollFirst 来从队首移除元素,或使用 peekFirst 来查看队首元素而不移除它。

重要的是要记住,无论你从哪一端开始添加元素,ArrayDeque 都允许你在另一端进行操作,这就是双端队列的灵活之处。所以,队首和队尾的概念将根据你的使用方式(即从哪一端添加或移除元素)而确定。

解决方案

初始化

首先,检查窗口大小是否合理(即不超过数组长度且不为0)。如果不合理,返回空结果。

使用双端队列处理窗口

我们使用双端队列来存储窗口中所有元素的索引,并保持队列中的元素值是递减的。这样,队列的头部始终是当前窗口的最大值。

窗口滑动过程
  1. 初始化窗口:首先处理滑动窗口的第一个位置。遍历窗口内的元素,确保双端队列中元素值递减。

  2. 滑动窗口:随着窗口向右滑动,更新双端队列。

    • 如果队列头部的元素不在窗口内(即索引小于当前索引减去窗口大小),则将其从队列中移除。

    • 从队列尾部移除所有小于当前元素值的索引,然后将当前元素的索引添加到队列尾部。

  3. 记录最大值:每次滑动后,队列的头部元素即为当前窗口的最大值。

实现代码

From.官方题解

import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size) {
        ArrayList<Integer> res = new ArrayList<Integer>();
        //窗口大于数组长度的时候,返回空
        if(size <= num.length && size != 0){
            //双向队列
            ArrayDeque <Integer> dq = new ArrayDeque<Integer>();  
            //先遍历一个窗口
            for(int i = 0; i < size; i++){
                //去掉比自己先进队列的小于自己的值
                while(!dq.isEmpty() && num[dq.peekLast()] < num[i])
                     dq.pollLast();
                dq.add(i);
            }
            //遍历后续数组元素
            for(int i = size; i < num.length; i++){
                //取窗口内的最大值
                res.add(num[dq.peekFirst()]);
                while(!dq.isEmpty() && dq.peekFirst() < (i - size + 1))
                    //弹出窗口移走后的值
                    dq.pollFirst(); 
                //加入新的值前,去掉比自己先进队列的小于自己的值
                while(!dq.isEmpty() && num[dq.peekLast()] < num[i])
                    dq.pollLast();
                dq.add(i);
            }
            res.add(num[dq.pollFirst()]);
        }     
        return res;
    }
}

ArrayDequeue方法说明

队列首部(Front)的操作

  1. addFirst(E e): 在双端队列的前面添加一个元素。如果双端队列已满,此方法将抛出一个 IllegalStateException

  2. offerFirst(E e): 在双端队列的前面添加一个元素。如果双端队列已满,则返回 false

  3. removeFirst(): 移除并返回双端队列的第一个元素。如果双端队列为空,此方法将抛出一个 NoSuchElementException

  4. pollFirst(): 移除并返回双端队列的第一个元素。如果双端队列为空,则返回 null

  5. peekFirst(): 返回双端队列的第一个元素,但不移除它。如果双端队列为空,则返回 null

队列尾部(Last)的操作

  1. addLast(E e): 在双端队列的末尾添加一个元素。如果双端队列已满,此方法将抛出一个 IllegalStateException

  2. offerLast(E e): 在双端队列的末尾添加一个元素。如果双端队列已满,则返回 false

  3. removeLast(): 移除并返回双端队列的最后一个元素。如果双端队列为空,此方法将抛出一个 NoSuchElementException

  4. pollLast(): 移除并返回双端队列的最后一个元素。如果双端队列为空,则返回 null

  5. peekLast(): 返回双端队列的最后一个元素,但不移除它。如果双端队列为空,则返回 null


总结

通过使用双端队列,我们可以高效地解决滑动窗口的最大值问题。双端队列的灵活性在于能够从两端对队列进行操作,这使得它在许多场景下都非常有用,尤其是在需要快速访问头部和尾部元素的情况下。

双端队列deque,全称Double Ended Queue)是一种可以在两端进行插入和删除元素的数据结构,它既支持在头部添加和移除元素,也支持在尾部进行操作。在解决滑动窗口问题时,可以利用这种数据结构高效地找到给定区间内的最大值和最小值。 以下是使用C++的`std::deque`实现滑动窗口最大值和最小值的一种常见方法: ```cpp #include <deque> #include <algorithm> // 定义滑动窗口最大值和最小值 struct WindowMinMax { std::deque<int> window; // 使用deque作为窗口 int max_val, min_val; void add(int num) { // 添加新元素到窗口 window.push_back(num); // 更新最大值和最小值 if (window.size() > 1) { // 窗口大小大于1 max_val = std::max(max_val, num); min_val = std::min(min_val, num); // 如果窗口满,移除左侧的第一个元素并更新最大值和最小值 if (window.front() == num - (window.size() - 1)) { window.pop_front(); max_val = std::max(window.front(), max_val); min_val = std::min(window.front(), min_val); } } else { max_val = num; min_val = num; } } void remove(int num) { // 移除定元素 if (!window.empty() && window.back() == num) { window.pop_back(); if (window.empty()) { max_val = min_val = INT_MIN; // 如果窗口为空,设最大值和最小值为整数最小 } else { max_val = *std::max_element(window.begin(), window.end()); min_val = *std::min_element(window.begin(), window.end()); } } } }; int main() { WindowMinMax window; // 示例操作:add(5), add(8), remove(5), add(10), remove(8) window.add(5); // 窗口[5] window.add(8); // 窗口[5, 8] window.remove(5); // 窗口[8] window.add(10); // 窗口[8, 10] window.remove(8); // 窗口[10] // 结果:max_val = 10, min_val = 8 return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值