单调栈篇
需要单调栈实现:依次遍历,需要找到单方向(从左开始,或从右开始)第一个比当前位置数大(递减栈)或数小(递增栈)的元素的情况。
LeetCode题目
739.每日温度(中等)
题目要求:
遍历每一个温度,寻找到第一个大于当天温度的天数,返回天数差距。这里可以看到满足需要单调栈的情况。
因为是找到第一个大于当天温度的温度,这里使用单调递减栈。
class Solution {
/**
* 单调栈
* 这里使用单调递减栈,因为要找到每个元素右侧第一个数大的元素
* 栈为空或当前元素元素小于栈顶:入栈。
* 当前元素元素大于栈顶:出栈直到栈顶元素小于当前元素或空栈,出栈的同时可以计算元素距离。之后再入栈。
* @param temperatures
* @return
*/
public int[] dailyTemperatures(int[] temperatures) {
// 单调递减栈
Deque<Integer> stack = new LinkedList<>();
int[] res = new int[temperatures.length];
for(int i = 0; i < temperatures.length; i++) {
// 栈空或栈顶 > 当前元素,则入栈
if(stack.isEmpty() || temperatures[stack.peek()] >= temperatures[i]) {
stack.push(i);
} else {
// 若栈顶 < 当前元素,则找到第一个大数,循环出栈计算直到栈顶 > 当前元素。
while(!stack.isEmpty() && temperatures[stack.peek()] < temperatures[i]) {
int index = stack.poll();
res[index] = i - index;
}
// 出完栈后进栈
stack.push(i);
}
}
// 遍历之后栈内剩余元素为右侧无数大的元素,全都赋值0即可。
while(!stack.isEmpty()) {
int index = stack.poll();
res[index] = 0;
}
return res;
}
}
42.接雨水(困难)
在计算容器面积时,我们也是维护一个单调递减栈,在遇到一个栈顶 < 当前元素高度时,形成了一个矩形容器,可以进行计算面积。此时栈顶为低的高度,栈顶下一层的元素为矩形的左边高,当前元素高度为右边高,取两边的短边计算面积。
复杂容器情况,根据单调栈的性质是按照行(以矩形为单位,可能由多行组成) 进行计算的。
class Solution {
/**
* 单调栈
* 运维一个单调递减栈,存储高度下标
* 1.当栈顶下标对应高度 > 当前元素:直接入栈
* 2.(当栈顶小标对应高度 == 当前元素:出栈,再入栈)这一步可以省略,第三步可以间接实现这一步。为什么相同高度只保留一个呢,这个是为了方便计算面积。
* 3.当栈顶下标对应高度 < 当前元素:形成凹槽,计算面积,因为凹槽有可能为多行矩形叠加,这里按行计算;
*/
public int trap(int[] height) {
int len = height.length;
if(len <= 2) {
return 0;
}
// stack stores index of array of height, initial stack push 0 the first index;
// 单调递减栈
Deque<Integer> stack = new LinkedList<>();
stack.push(0);
int res = 0;
for(int i = 1; i < len; i++) {
if(height[i] < height[stack.peek()]) {
stack.push(i);
} else if(height[i] == height[stack.peek()]) {
stack.pop();
stack.push(i);
} else {
int right = i;
// stack.peek() is mid, when height[right] > height[mid], stack pop mid and then stack.peek is left;
// 循环判断当前的right是否大于mid,若大于mid出栈,随后判断left是否存在(栈是否为空),如存在计算面积
while(!stack.isEmpty() && height[right] > height[stack.peek()]) {
// mid is height of bottom;mid是底部高度
int mid = stack.pop();
// find left if left exit;
if(!stack.isEmpty()) {
// left是mid的栈下层元素,一定是 > mid的高度的,是潜在的面积计算高度
int left = stack.peek();
// 比较height[left],height[right]找到矩形短板高度,再减去底部高度height[mid],即为这一行的举行高度
int h = Math.min(height[left], height[right]) - height[mid];
int w = right - left - 1;
res += h * w;
}
}
stack.push(i);
}
}
return res;
}
}
239.滑动窗口最大值(困难)
题解
这里和之前的题目不同,因为滑动窗口涉及左出(元素过期),右出右进(单调的保持),所以不能用栈实现,要用双端队列。
每一次窗口移动,都要找到窗口内的最大值,所以需要一个单调递减双端队列存放元素下标。
遍历元素,当前队列为空或队列尾 > 当前元素,则入队。
若队列尾 < 当前元素,则不断出队直到满足上一行条件,再入队。
队头根据下标范围判定是否过期,若过期弹出队头。单调递减保持队头始终为最大值,若当前窗口内元素值满足窗口大小,保存队头对应值即可。
class Solution {
/**
* 双端队列
* 使用双端队列存储元素下标,这是因为要通过下标判断队首是否过期,通过下标也能访问到数据;
* 双端队列中下标的存放顺序,是按照下标对应元素的大小单调递减存放。若要加入的元素大于队尾对应元素,则移除队尾,直到满足单调递减;
* 队列添加完元素后,要判断队首元素是否过期,这里就会用到下标。队首元素是否大于当前访问元素下标i - 窗口大小k,若不是则过期出队首。
* 访问元素下标 >= 窗口时,窗口填满,可以按照队首存储当前窗口最大值至result数组中。
* T(n) = O(n)
* S(n) = O(k)
* @param nums
* @param k
* @return
*/
public int[] maxSlidingWindow(int[] nums, int k) {
// 双端队列存储元素下标,
Deque<Integer> queue = new LinkedList<>();
int[] result = new int[nums.length - k + 1];
for(int i = 0; i < nums.length; i++) {
while(!queue.isEmpty() && nums[queue.peekLast()] < nums[i]) {
queue.pollLast();
}
queue.addLast(i);
// 这里while和if的效果一样,因为每一次窗口移动最多过期一位元素
if(queue.peek() <= i - k) {
queue.poll();
}
if(i + 1 >= k) {
result[i + 1 - k] = nums[queue.peek()];
}
}
return result;
}
}