剑指 Offer 59 - I. 滑动窗口的最大值
单调队列
思路 🤔
- 维护一个队列,其单调性为“从队头到队尾单调减”,可对其操作如下:
add(int val)
:若当前元素 v a l val val>
队尾元素,则需要维护单调性,即队尾出队,直至满足单调性为止(从队头到队尾单调减);否则,则将当前元素 v a l val val 直接插入队尾,即可remove(int val)
:若当前元素 v a l val val==
队头元素,则将队头元素出队;否则,不做任何操作。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length < k || k == 0)
return new int[] {};
int n = nums.length;
int[] res = new int[n - k + 1];
// 先处理前 k - 1 个
MyQueue queue = new MyQueue();
int i = 0;
int index = 0;
for ( ; i < k - 1; i++) {
queue.add(nums[i]);
}
for ( ; i < n; i++) {
queue.add(nums[i]);
res[index++] = queue.peek();
queue.remove(nums[i - k + 1]);
}
return res;
}
}
class MyQueue {
Deque<Integer> queue = new LinkedList<>();
public void add(int val) {
// 维护单调性(从队头到队尾单调减)
while (!queue.isEmpty() && val > queue.peekLast()) { // 和队尾比较
queue.pollLast();
}
queue.offer(val);
}
public int peek() {
if (queue.isEmpty()) {
throw new NullPointerException();
}
return queue.peek();
}
public void remove(int val) {
if (queue.isEmpty()) {
throw new NullPointerException();
}
// 窗口左端元素和对头元素相同时,则队头出队
if (val == this.peek()) {
queue.poll();
}
}
}
- 使用双端队列,而不是单端队列;
- 维护单调性时(从队头到队尾单调减),是将当前元素 v a l val val 和队尾元素比较(而不是队头元素)
剑指 Offer 59 - II. 队列的最大值
双队列:单调队列 + 普通队列
一种朴素的解法是,使用一个普通队列,push_back
和 pop_front
的时间复杂度均为
O
(
1
)
O(1)
O(1)。
但是,此时 max_value
需要遍历队列中所有元素,时间复杂度为
O
(
n
)
O(n)
O(n)
更优的解法是:使用一个普通队列,同时额外再维护一个 单调队列,具体步骤如下。
思路 🤔
- 额外维护一个单调队列
descendQueue
,其单调性为“从队头到队尾单调减” max_value()
:单调队列descendQueue
队头元素,即为当前最大元素;push_back(int value)
:入队- 首先,需要维护
descendQueue
的单调性(队头到队尾单调递减) - 然后,将
v
a
l
u
e
value
value 同时入队
queue
和descendQueue
- 首先,需要维护
pop_front()
:出队- 队列
queue
为空时,直接返回 − 1 -1 −1,即可; - 若
descendQueue
队头元素和queue
队头元素相等,则二者队头元素同时出队; - 否则,即两个队头不相等,则仅仅
queue
队头出队。
- 队列
class MaxQueue {
Deque<Integer> queue = new LinkedList<>();
Deque<Integer> descendQueue = new LinkedList<>();
public MaxQueue() {
}
public int max_value() {
if (descendQueue.isEmpty()) {
return -1;
}
return descendQueue.peekFirst();
}
public void push_back(int value) {
// 维护descendQueue的单调性(对头到对尾单调递减)
while (!descendQueue.isEmpty()
&& value > descendQueue.peekLast()) {
descendQueue.pollLast();
}
// 同时入队
queue.offerLast(value);
descendQueue.offerLast(value);
}
public int pop_front() {
if (queue.isEmpty()) {
return -1;
}
if (!descendQueue.isEmpty()
&& descendQueue.peek().equals(queue.peek())) {
queue.pollFirst();
return descendQueue.pollFirst();
}
return queue.pollFirst();
}
}
/**
* Your MaxQueue object will be instantiated and called as such:
* MaxQueue obj = new MaxQueue();
* int param_1 = obj.max_value();
* obj.push_back(value);
* int param_3 = obj.pop_front();
*/