废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是【单调栈的应用】,使用【栈】这个基本的数据结构来实现,这个高频题的站点是:CodeTop,筛选条件为:目标公司+最近一年+出现频率排序,由高到低的去牛客TOP101去找,只有两个地方都出现过才做这道题(CodeTop本身汇聚了LeetCode的来源),确保刷的题都是高频要面试考的题。
明确目标题后,附上题目链接,后期可以依据解题思路反复快速练习,题目按照题干的基本数据结构分类,且每个分类的第一篇必定是对基础数据结构的介绍。
每日温度是单调栈的典型应用题
初始化result的长度与原数组长度相同,如果后续无结果录入则自动补0
给出代码实现基本档案
基本数据结构:栈
辅助数据结构:无
算法:无
技巧:单调栈
其中数据结构、算法和技巧分别来自:
当然包括但不限于以上
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param n int整型 the n
* @return int整型
*/
public int[] dailyTemperatures(int[] temperatures) {
// 1 入参判断,如果数组为空,则结果集为空数组
if (temperatures == null || temperatures.length == 0) {
return new int[temperatures.length];
}
// 2 定义单调递减栈,单调栈存储当前温度下标,和返回结果集
Stack<Integer> singleStack = new Stack<Integer>();
int[] result = new int[temperatures.length];
// 3 遍历数组,进行温度结算
for (int i = 0; i < temperatures.length; i++) {
// 如果满足结算条件,进行结算
while (!singleStack.isEmpty() && temperatures[i] > temperatures[singleStack.peek()]) {
// 1 弹出并记录当前温度记录时间
int cur = singleStack.pop();
// 2 计算更大温度时间与当前温度时间差
result[cur] = i - cur;
}
// 结算完毕后元素入栈
singleStack.push(i);
}
return result;
}
}
时间复杂度O(n):虽然 while 循环里套了一个 while 循环,但是考虑到每个元素最多访问两次,入栈一次和出栈一次,所以时间复杂度是 O(n)。
空间复杂度O(n):O(n)。栈的空间
千呼万唤始出来,经典题目接雨水,使用单调栈来解决
原题解地址,单调栈就是保持栈内元素有序。和单调队列一样,需要我们自己维持顺序,没有现成的容器可以用。通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。
而接雨水这道题目,我们正需要寻找一个元素,右边最大元素以及左边最大元素,来计算雨水面积,首先单调栈是按照行方向来计算雨水
从大到小还是从小到大呢?从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序。因为一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子
如果遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。例如 5 5 1 3
这种情况。如果添加第二个5的时候就应该将第一个5的下标弹出,把第二个5添加到栈中。因为我们要求宽度的时候 如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度
使用单调栈,也是通过 长 * 宽 来计算雨水面积的。长就是通过柱子的高度来计算,宽是通过柱子之间的下标来计算,那么栈里有没有必要存一个pairheight[stack.top()]
就知道弹出的下标对应的高度了
所以栈的定义如下:
stack<int> st; // 存着下标,计算的时候用下标对应的柱子高度
以下逻辑主要就是三种情况
height[i] < height[st.top()]
height[i] == height[st.top()]
height[i] > height[st.top()]
然后开始从下标1开始遍历所有的柱子,for (int i = 1; i < height.size(); i++)
。如果当前遍历的元素(柱子)高度小于栈顶元素的高度,就把这个元素加入栈中,因为栈里本来就要保持从小到大的顺序(从栈头到栈底)。代码如下
if (height[i] < height[st.top()]) st.push(i);
如果当前遍历的元素(柱子)高度等于栈顶元素的高度,要跟更新栈顶元素,因为遇到相相同高度的柱子,需要使用最右边的柱子来计算宽度。
if (height[i] == height[st.top()]) { // 例如 5 5 1 7 这种情况
st.pop();
st.push(i);
}
如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了
height[mid]
(就是图中的高度1)。st.top()
,就是凹槽的左边位置,下标为st.top()
,对应的高度为height[st.top()](就是图中的高度2)。height[i]
(就是图中的高度3)。此时应该可以发现其实就是栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水!
int h = min(height[st.top()], height[i]) - height[mid];
int w = i - st.top() - 1 ;
h * w。
求当前凹槽雨水的体积代码如下:
while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while,持续更新栈顶元素
int mid = st.top();
st.pop();
if (!st.empty()) {
int h = min(height[st.top()], height[i]) - height[mid];
int w = i - st.top() - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
简化后的流程如下
继续结算5对应的雨水
4和6高度相同,无需结算,继续寻找高墙
寻找到高墙后继续结算
给出代码实现基本档案
基本数据结构:栈
辅助数据结构:无
算法:无
技巧:单调栈
其中数据结构、算法和技巧分别来自:
当然包括但不限于以上
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* max water
* @param arr int整型一维数组 the array
* @return long长整型
*/
public long maxWater (int[] arr) {
// 1 入参判断,数组为空,结算0个单位雨水
if (arr == null || arr.length == 0) {
return 0;
}
// 2 定义单调栈,存储元素下标,定义总的雨水单位
Stack<Integer> singleStack = new Stack<Integer>();
int result = 0;
// 3 遍历数组,进行雨水计算
for (int i = 0; i < arr.length; i++) {
// 右边的墙大于当前栈顶元素,满足结算条件,进行结算
while (!singleStack.isEmpty() && arr[i] > arr[singleStack.peek()]) {
// 1 当前栈顶元素出栈
int bottom = singleStack.pop();
// 如果当前栈顶只有一个元素,弹出后栈空,则无需结算,因为左边界没有墙无法蓄水
if (singleStack.isEmpty()) {
break;
}
// 2 bottom已弹出,此时的栈顶元素为左边墙,当前元素为右边界
int left = singleStack.peek();
int right = i;
// 3 分别计算高度和宽度
int height = Math.min(arr[left], arr[i]) - arr[bottom];
int weight = i - left - 1;
// 4 结算雨水单位并累加
result += height * weight;
}
// 结算完毕后,继续存储单调递减元素
singleStack.push(i);
}
return result;
}
}
时间复杂度O(n):虽然 while 循环里套了一个 while 循环,但是考虑到每个元素最多访问两次,入栈一次和出栈一次,所以时间复杂度是 O(n)。
空间复杂度O(n):O(n)。栈的空间
普通栈和单调栈都是数据结构,用于在计算机编程中处理和解决各种问题。它们的主要区别在于它们的行为方式和用途。
普通栈(Regular Stack):
单调栈(Monotonic Stack):
单调栈的主要思想是利用栈来维护特定的单调性,以快速查找和处理与这种单调性相关的问题。普通栈则是一种通用的数据结构,没有特定的单调性要求,用途更广泛。