单调栈与单调队列
问题
单调栈和单调队列的原理和常见题目?
答案
单调栈
维护一个栈底到栈顶单调递增/递减的栈,用于求每个元素左/右边第一个更大/更小的元素。
下一个更大元素(LeetCode 496/503)
单调递减栈(栈底到栈顶递减)
public int[] nextGreaterElement(int[] nums) {
int n = nums.length;
int[] result = new int[n];
Arrays.fill(result, -1);
Deque<Integer> stack = new ArrayDeque<>(); // 存索引
for (int i = 0; i < n; i++) {
// 当前元素比栈顶大,栈顶找到了右边第一个更大元素
while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) {
result[stack.pop()] = nums[i];
}
stack.push(i);
}
return result;
}
每日温度(LeetCode 739)
单调栈求右边第一个更大值的距离
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] result = new int[n];
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
int idx = stack.pop();
result[idx] = i - idx;
}
stack.push(i);
}
return result;
}
接雨水(LeetCode 42)—— 单调栈解法
单调递减栈,遇到更高的柱子计算积水
public int trap(int[] height) {
Deque<Integer> stack = new ArrayDeque<>();
int water = 0;
for (int i = 0; i < height.length; i++) {
while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
int bottom = stack.pop();
if (stack.isEmpty()) break;
int left = stack.peek();
int w = i - left - 1;
int h = Math.min(height[left], height[i]) - height[bottom];
water += w * h;
}
stack.push(i);
}
return water;
}
柱状图中最大的矩形(LeetCode 84)
单调递增栈,求每个柱子能扩展的最大宽度
public int largestRectangleArea(int[] heights) {
int n = heights.length;
Deque<Integer> stack = new ArrayDeque<>();
int maxArea = 0;
for (int i = 0; i <= n; i++) {
int h = (i == n) ? 0 : heights[i];
while (!stack.isEmpty() && h < heights[stack.peek()]) {
int height = heights[stack.pop()];
int width = stack.isEmpty() ? i : i - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
return maxArea;
}
单调队列 —— 滑动窗口最大值(LeetCode 239)
单调递减双端队列
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> deque = new ArrayDeque<>(); // 存索引,保持对应值单调递减
int[] result = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
// 移除超出窗口的元素
while (!deque.isEmpty() && deque.peekFirst() <= i - k) {
deque.pollFirst();
}
// 维护单调性:弹出比当前值小的
while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
if (i >= k - 1) result[i - k + 1] = nums[deque.peekFirst()];
}
return result;
}
常见面试问题
Q1: 单调栈和普通栈的区别?
答案:
普通栈不限制元素大小关系;单调栈在入栈时维护单调性,遇到破坏单调性的元素时弹出并处理。本质是用栈来高效找到每个元素的左/右边界。
Q2: 什么时候用单调栈,什么时候用单调队列?
答案:
| 数据结构 | 适用场景 | 典型题 |
|---|---|---|
| 单调栈 | 求左/右边第一个更大/更小元素 | 每日温度、接雨水、最大矩形 |
| 单调队列 | 滑动窗口内的最值 | 滑动窗口最大值 |
Q3: 单调栈的时间复杂度?
答案:
。每个元素最多入栈一次、出栈一次,总操作 。