1. 每日温度
- 题目:https://leetcode.cn/problems/daily-temperatures/description/
- 思路:单调栈,栈顶到栈底单调递增,栈内存放温度的索引;每加入一个新的元素,如过栈顶元素对应的温度小于当前温度,那么栈顶元素右边第一个比它高的温度就找到了。
- 代码实现:
// 单调栈
// 对于每个元素要找它右边第一个比他大的元素,那么对于每一个已经入栈的元素,当后面的元素加入时
// 如果比他大,那么就将栈顶元素出栈,同时计算出栈元素右边第一个比他大的元素的距离
// 如果小于等于,直接入栈。栈内一直保持栈顶到栈底单调递增
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int length = temperatures.length;
int[] res = new int[length];
// 栈 -- 里面放数组元素的索引,值可以直接通过数组取,索引方便计算距离
Deque<Integer> stack = new LinkedList<>();
stack.add(0);
// 遍历,维持单调栈
for(int i = 1; i < length; ++i) {
// 判断栈顶与当前元素的关系
if(temperatures[stack.peek()] < temperatures[i]) {
// 栈顶小于当前元素,那么栈顶元素右边第一个比他大的出现了
// 栈顶弹出加入新元素以维持单调栈,并计算距离
// 同时新的栈顶也要进行判断
while(!stack.isEmpty() && temperatures[stack.peek()] < temperatures[i]) {
// 计算距离
res[stack.peek()] = i - stack.peek();
// 栈顶弹出
stack.pop();
}
// 当前元素加入
stack.push(i);
}
else {
// 栈顶元素 >= 当前元素,直接入栈
stack.push(i);
}
}
// 最后单调栈内留下的元素都没有右边比它大的,在数组初始化时已经初始化为0
return res;
}
}
2. 下一个更大元素I
- 题目:https://leetcode.cn/problems/next-greater-element-i/submissions/
- 思路:单调栈,相比每日温度只是多了一个要找 nums1 中的元素在 nums2 中 – 依然是对 nums2 进行单调栈相关遍历,只是在出现右边第一个比当前数字大的时,要判断栈顶元素是否在 nums1 出现 – 用哈希判断要弹出的栈顶元素是否在 nums1 中出现过。没有右边比他大的数字结果是 -1,可以先将 res 数组填充为 -1。
- 代码实现:
// 单调栈,相比每日温度只是多了一个要找 nums1 中的元素在 nums2中
// 那么依然时对 nums2 进行单调栈相关遍历,只是在出现右边第一个比当前数字大的时,要判断栈顶元素是否在 nums1 出现
// 判断要弹出的栈顶元素是否在 nums1 出现 -- 哈希
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
// 存放结果的数组
int len = nums1.length;
int[] res = new int[len];
// 给 res 全填充为 -1
Arrays.fill(res, -1);
// 哈希映射 -- 拿值找索引
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < len; ++i) {
map.put(nums1[i], i);
}
// 栈,里面直接装数就行了,对应索引在哈希中
Stack<Integer> stack = new Stack<>();
stack.push(nums2[0]);
// 遍历并维护单调栈 -- 栈顶到栈底单调递增
for(int i = 1; i < nums2.length; ++i) {
if(nums2[i] > stack.peek()) {
// 进来了更大的元素 -- while 循环重复进行栈顶的弹出
while(!stack.isEmpty() && nums2[i] > stack.peek()) {
// 看一下栈顶元素是否在哈希中存在,不存在就不需要更新结果数组
if(map.containsKey(stack.peek())) {
// 存在 -- 取出索引,然后更新 res
res[map.get(stack.peek())] = nums2[i];
}
// 栈顶出栈
stack.pop();
}
stack.push(nums2[i]);
}
else{
stack.push(nums2[i]);
}
}
return res;
}
}
3. 下一个更大元素 II
- 题目:https://leetcode.cn/problems/next-greater-element-ii/submissions/
- 思路:单调栈,特别之处在于是一个循环数组 – 将数组“拼接”两个到一起就可以了。只用在遍历的时候遍历 nums 长度的2倍,然后元素的选取可以通过 i % nums.length,从而实现模拟“拼接”。
- 代码实现:
// 单调栈,特别之处在于是一个循环数组 -- 将数组“拼接”两个到一起就可以了
// 只用在遍历的时候遍历 nums 长度的2倍,然后元素的选取可以通过 i % nums.length,从而实现模拟“拼接”
class Solution {
public int[] nextGreaterElements(int[] nums) {
int size = nums.length;
// 结果数组
int[] res = new int[size];
Arrays.fill(res, -1);
// 栈 -- 存放索引,因为 res 中需要索引来放置结果
Stack<Integer> stack = new Stack<>();
stack.add(0);
// 遍历,同时维护单调栈 -- 遍历 2 倍 nums.length,模拟拼接表示循环数组
for(int i = 1; i < size * 2; ++i) {
// nums、res中的索引遍历时都用 i % size
if(nums[stack.peek()] < nums[i % size]) {
while(!stack.isEmpty() && nums[stack.peek()] < nums[i % size]) {
// 栈顶元素的结果得到了
res[stack.peek()] = nums[i % size];
stack.pop();
}
stack.push(i % size);
}
else {
stack.push(i % size);
}
}
return res;
}
}
4. 接雨水
- 题目:https://leetcode.cn/problems/trapping-rain-water/submissions/
- 思路:单调栈:按照行来计算接到的雨水体积。
-
栈内按柱子的高低,从栈顶到栈底升序排列柱子对应的索引
-
1、当新遍历到的柱子高于栈顶的柱子,那么对于栈顶元素来说,他现在是一个凹槽(要注意左边可能没有柱子了)。出现凹槽就计算按照“行”能接到雨水的体积 – 当前这里形成的行凹槽,当前栈顶就作为这一行的底部高度bottom,左边能到栈顶元素弹出后新的栈顶,右边能到当前遍历到的位置,那么二者坐标差就是宽度,高度直接就是左右两边高度的小者和底部高度的高度差;
-
2、当前遍历到的柱子与栈顶相等,弹出栈顶,因为相等的两个柱子之间不可能存放雨水
-
3、当前遍历到的柱子小于栈顶元素,入栈
-
- 代码实现:
// 单调栈:按照行来计算接到的雨水体积
// 栈内按柱子的高低,从栈顶到栈底升序排列柱子对应的索引
// 1、当新遍历到的柱子高于栈顶的柱子,那么对于栈顶元素来说,他现在是一个凹槽(要注意左边可能没有柱子了)
// 出现凹槽就计算按照“行”能接到雨水的体积
// 那么当前这里形成的行凹槽,左边能到栈顶元素弹出后新的栈顶,右边能到当前遍历到的位置,那么二者坐标差就是宽度
// 高度直接就是左右两边高度的小者和栈顶柱子的高度差
// 2、当前遍历到的柱子与栈顶相等,弹出栈顶,因为相等的两个柱子之间不可能存放雨水
// 3、当前遍历到的柱子小于栈顶元素,入栈
class Solution {
public int trap(int[] height) {
// 1、记录雨水总量
int res = 0;
// 2、栈,存放柱子的索引,高度通过数组拿索引取出
Stack<Integer> stack = new Stack<>();
stack.push(0);
// 3、遍历,同时保持栈按照柱子高度从栈顶到栈底升序
for(int i = 1; i < height.length; ++i) {
if(height[i] > height[stack.peek()]) {
// 3.1 情况1,形成了凹槽
while(!stack.isEmpty() && height[i] > height[stack.peek()]) {
// 栈顶弹出作为底
int bottom = stack.pop();
// 确保左边是还有柱子从而能形成接水控件
if(!stack.isEmpty()) {
// 能接水,计算
// 高度 -- 左(当前新的栈顶)、右(当前遍历到的)中的小者与底的差
int heightDiff = Math.min(height[stack.peek()], height[i]) - height[bottom];
// 宽度,左右位置差
int widthDiff = i - stack.peek() - 1;
// 计算这一“行”接到水的体积
res += heightDiff * widthDiff;
}
}
// 入栈
stack.push(i);
}
else if(height[i] == height[stack.peek()]) {
// 3.2 情况2,两者相等,弹出栈顶,放入新的;因为两个相等的之间不可能接水
stack.pop();
stack.push(i);
}else {
// 3.3 情况3,当前小于栈顶,直接入栈,等着可能形成凹槽
stack.push(i);
}
}
return res;
}
}
5. 柱状图中最大的矩形
- 题目:https://leetcode.cn/problems/largest-rectangle-in-histogram/submissions/
- 思路:单调栈:对于每个柱子,以他的高度能形成的最大矩形是左右到小于他的高度为宽,他自己的高度为高。
- 那么对于每个在栈顶的元素,它能够计算时应当是它右边的比它低的柱子被遍历然后要进来了,此时就应当弹出栈顶来计算它所能形成的最大矩形,然后此时借助单调栈的特性,新的栈顶就应该是左边比它低的索引。从而得到单调栈从栈顶到栈底应当单调递减。
- 注意此时整个数组单调递增,那么最后结束的位置也不会触发计算矩形面积的条件,所以要在最后补一个0;同理,如果整个数组单调递减,计算第一个元素形成的面积时左边没有则计算的是0,所以要在数组开头补一个0
- 代码实现:
// 单调栈:对于每个柱子,以他的高度能形成的最大矩形是左右到小于他的高度为宽,他自己的高度为高
// 那么对于每个在栈顶的元素,它能够计算时应当是它右边的比它低的柱子被遍历然后要进来了
// 此时就应当弹出栈顶来计算它所能形成的最大矩形,然后此时借助单调栈的特性,新的栈顶就应该是左边比它低的索引
// 从而得到单调栈从栈顶到栈底应当单调递减
// 注意此时整个数组单调递增,那么最后结束的位置也不会触发计算矩形面积的条件,所以要在最后补一个0
// 同理,如果整个数组单调递减,计算第一个元素形成的面积时左边没有则计算的是0,所以要在数组开头补一个0
class Solution {
public int largestRectangleArea(int[] heights) {
// 1、数组开头结尾补0
int[] newHeights = new int[heights.length + 2];
newHeights[0] = 0;
newHeights[newHeights.length - 1] = 0;
// 拷贝heights过来
for(int i = 0; i < heights.length; ++i) {
newHeights[i + 1] = heights[i];
}
// 2、存放结果的变量
int res = 0;
// 3、栈,栈顶到栈底单调递减,存放索引
Stack<Integer> stack = new Stack<>();
stack.push(0);
// 4、遍历
for(int i = 1; i < newHeights.length; ++i) {
if(newHeights[stack.peek()] > newHeights[i]) {
// 4.1 情况1,对于栈顶元素,找到了右边比它低的
while(newHeights[stack.peek()] > newHeights[i]) {
int height = newHeights[stack.pop()];
int width = i - stack.peek() - 1;
res = Math.max(res, height * width);
}
stack.push(i);
}
else if(newHeights[stack.peek()] == newHeights[i]) {
// 4.2 情况2,相等,可以弹出用新的位置来计算,都是这个高度能形成的最高的
stack.pop();
stack.push(i);
}
else {
// 4.3 情况3,当前元素高于栈顶,直接入栈
stack.push(i);
}
}
// 5、返回结果
return res;
}
}