5,队列-设计一个双端队列,滑动 窗口最大值

154 阅读1分钟

队列概念

队列这个概念非常好理解。你可以把它想象成排队买票,先来的先买,后来的人只能站末尾,不允许插队。先进者先出,这就是典型的“队列

队列相关的必会代码实现:

  • 用数组实现一个顺序队列

    public class ArrayQueueT { private int [] queue; private int front; private int rear; private int capacity; public ArrayQueueT(int capacity) { this.capacity = capacity; queue = new int[capacity]; front = 0; rear = -1; } public void enqueue(int item){ if(rear ==capacity-1){ throw new RuntimeException("queue is full"); } rear ++; queue[rear] = item; } public int dequeue(){ if(isEmpty()){ throw new RuntimeException("queue is empty"); } int item = queue[front]; front++; if(front > rear){ front = 0; rear = 1; } return item; } public int peek(){ if(isEmpty()){ throw new RuntimeException("queue is empty"); } return queue[front]; } public int size(){ return rear - front + 1; } public boolean isEmpty(){ return rear == -1 || front >rear; }}

  • 用链表实现一个链式队列

    public class CircularQueue { private int[] queue; private int head; private int tail; private int capacity; public CircularQueue(int capacity) { queue = new int[capacity]; head = 0; tail = 0; this.capacity = capacity; } public void enqueue(int value) { if (isFull()) { throw new RuntimeException("queue is full"); } tail = (tail + 1) % capacity; queue[tail] = value; } public int dequeue() { if (isEmpty()) { throw new RuntimeException("queue is empty"); } int value = queue[head]; head = (head + 1) % capacity; return value; } private boolean isFull() { return (tail + 1) % capacity == head; } private boolean isEmpty() { return head == tail; } public int size() { if (isEmpty()) { return 0; } if (head < tail) { return tail - head; } return capacity - head + tail; } }

  • 实现一个循环队列

    public class CircularQueue { private int[] queue; private int head; private int tail; private int capacity; public CircularQueue(int capacity) { queue = new int[capacity]; head = 0; tail = 0; this.capacity = capacity; } public void enqueue(int value) { if (isFull()) { throw new RuntimeException("queue is full"); } tail = (tail + 1) % capacity; queue[tail] = value; } public int dequeue() { if (isEmpty()) { throw new RuntimeException("queue is empty"); } int value = queue[head]; head = (head + 1) % capacity; return value; } private boolean isFull() { return (tail + 1) % capacity == head; } private boolean isEmpty() { return head == tail; } public int size() { if (isEmpty()) { return 0; } if (head < tail) { return tail - head; } return capacity - head + tail; }}

1,设计循环双端队列

题目:

实现 MyCircularDeque 类:

  • MyCircularDeque(int k) :构造函数,双端队列最大为 k

  • boolean insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true ,否则返回 false

  • boolean insertLast() :将一个元素添加到双端队列尾部。如果操作成功返回 true ,否则返回 false

  • boolean deleteFront() :从双端队列头部删除一个元素。 如果操作成功返回 true ,否则返回 false

  • boolean deleteLast() :从双端队列尾部删除一个元素。如果操作成功返回 true ,否则返回 false

  • int getFront() ):从双端队列头部获得一个元素。如果双端队列为空,返回 -1

  • int getRear() :获得双端队列的最后一个元素。 如果双端队列为空,返回 -1

  • boolean isEmpty() :若双端队列为空,则返回 true ,否则返回 false

  • boolean isFull() :若双端队列满了,则返回 true ,否则返回 false

    class MyCircularDeque { private int[] deque; private int front; private int last; private int capacity; public MyCircularDeque(int k) { deque = new int[k+1]; front = 0; last = 0; this.capacity = k+1; } public boolean insertFront(int value) { if (isFull()) { return false; } front= (front -1 +capacity) % capacity; deque[front] = value; return true; } public boolean insertLast(int value) { if (isFull()) { return false; } deque[last] = value; last = (last + 1) % capacity; return true; } public boolean deleteFront() { if (isEmpty()) { return false; } front = (front + 1) % capacity; return true; } public boolean deleteLast() { if (isEmpty()) { return false; } last = (last-1+capacity) %capacity; return true; } public int getFront() { if (isEmpty()) { return -1; } return deque[front]; } public int getRear() { if (isEmpty()) { return -1; } return deque[(last-1+capacity) %capacity]; } public boolean isEmpty() { return last == front; } public boolean isFull() { return (last +1) % capacity == front; }}

2,滑动 窗口最大值

题目:

给你一个整数数组 nums,有一个大小为 k

的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回

滑动窗口中的最大值

题解:优先队列

什么是优先队列:

优先队列(Priority Queue)是一种特殊的队列数据结构,它可以根据元素的优先级进行插入和删除操作。与普通队列不同,优先队列中的元素并不是按照插入的顺序进行访问,而是根据元素的优先级进行排序。

优先队列可以用于解决许多与优先级相关的问题,例如找到最大或最小的元素,按照优先级处理任务等。它提供了以下两个主要操作:

  1. 插入操作:将一个元素插入到优先队列中。插入操作会根据元素的优先级将元素放置在合适的位置,以保持队列的有序性。

  2. 删除操作:从优先队列中移除并返回优先级最高(或最低)的元素。删除操作会将队列中的头部元素(优先级最高的元素)移除,并返回该元素。

对于「最大值」,我们可以想到一种非常合适的数据结构,那就是优先队列(堆),其中的大根堆可以帮助我们实时维护一系列元素中的最大值。 

 对于本题而言,初始时,我们将数组 nums的前 k个元素放入优先队列中。

每当我们向右移动窗口时,我们就可以把一个新的元素放入优先队列中,此时堆顶的元素就是堆中所有元素的最大值。

然而这个最大值可能并不在滑动窗口中,在这种情况下,这个值在数组 nums中的位置出现在滑动窗口左边界的左侧。因此,当我们后续继续向右移动窗口时,这个值就永远不可能出现在滑动窗口中了,我们可以将其永久地从优先队列中移除。 我们不断地移除堆顶的元素,直到其确实出现在滑动窗口中。此时,堆顶元素就是滑动窗口中的最大值。

为了方便判断堆顶元素与滑动窗口的位置关系,我们可以在优先队列中存储二元组 (num,index),表示元素 num 在数组中的下标为 index。 

public int[] maxSlidingWindowTest(int [] nums,int k){    int n= nums.length;    if(n==0){        return new int[0];    }    int [] ans;    if(k<n){        ans = new int [n-k+1];    }else{        ans = new int [1];    }    PriorityQueue<int []> pq = new PriorityQueue<>(((o1, o2) -> o1[0] != o2[0] ? o2[0]-o1[0]:o2[1]-o1[1]));    for(int i=0;i<k;i++){        pq.offer(new int[]{nums[i],i});    }    ans[0] = pq.peek()[0];    for(int i=k;i<n;i++){        pq.offer(new int[]{nums[i],i});        while(pq.peek()[1] < i-k){            pq.poll();        }        ans[i-k+1] = pq.peek()[0];    }    return ans;}