力扣题目链接
题目
给定一个整数数组 temperatures
,表示每天的温度,返回一个数组 answer
,其中 answer[i] 是指在第 i 天之后,才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
示例 3:
输入: temperatures = [30,60,90]
输出: [1,1,0]
提示:
1 <= temperatures.length <= 10^5
30 <= temperatures[i] <= 100
思路
使用单调栈主要有三个判断条件。
T[st.top()]
的情况T[st.top()]
的情况T[st.top()]
的情况接下来我们用temperatures = [73, 74, 75, 71, 71, 72, 76, 73]为例来逐步分析,输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
首先先将第一个遍历元素加入单调栈
加入T[1] = 74,因为T[1] > T[0]
(当前遍历的元素T[i]大于栈顶元素 T[st.top( )] 的情况),而我们要保持一个递增单调栈(从栈头到栈底),所以将T[0]弹出,T[1]加入,此时result数组可以记录了,result[0] = 1,即T[0]右面第一个比T[0]大的元素是T[1]。
加入T[2],同理,T[1]弹出
加入T[3],T[3] < T[2] (当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况),加T[3]加入单调栈。
加入T[4],T[4] == T[3] (当前遍历的元素T[i]等于栈顶元素T[st.top( )]的情况),此时依然要加入栈,不用计算距离,因为我们要求的是右面第一个大于本元素的位置,而不是大于等于!
加入T[5],T[5] > T[4] (当前遍历的元素T[i]大于栈顶元素T[st.top( )]的情况),将T[4]弹出,同时计算距离,更新result
T[4]弹出之后, T[5] > T[3] (当前遍历的元素T[i]大于栈顶元素T[st.top( )]的情况),将T[3]继续弹出,同时计算距离,更新 result
直到发现T[5]小于T[st.top( )],终止弹出,将T[5]加入单调栈
加入T[6],同理,需要将栈里的T[5],T[2]弹出
同理,继续弹出
此时栈里只剩下了T[6]
加入T[7], T[7] < T[6] 直接入栈,这就是最后的情况,result数组也更新完了。
此时有同学可能就疑惑了,那 result[6] , result[7]怎么没更新啊,元素也一直在栈里。
其实定义result数组的时候,就应该直接初始化为0,如果result没有更新,说明这个元素右面没有更大的了,也就是为0。
以上在图解的时候,已经把,这三种情况都做了详细的分析。
T[st.top()]
的情况T[st.top()]
的情况T[st.top()]
的情况代码实现
/**
* https://leetcode-cn.com/problems/daily-temperatures/
*
* @author xiexu
* @create 2022-04-15 22:41
*/
public class _739_每日温度 {
public int[] dailyTemperatures(int[] temperatures) {
// 结果数组
int[] result = new int[temperatures.length];
Stack<Integer> stack = new Stack<>();
stack.push(0);
for (int i = 1; i < temperatures.length; i++) {
if (temperatures[i] < temperatures[stack.peek()]) { // 情况一
stack.push(i);
} else if (temperatures[i] == temperatures[stack.peek()]) { // 情况二
stack.push(i);
} else {
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) { // 情况三
result[stack.peek()] = i - stack.peek();
stack.pop();
}
stack.push(i);
}
}
return result;
}
}
力扣题目链接
题目
nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。
返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。
示例 1:
输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
- 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。
- 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
示例 2:
输入:nums1 = [2,4], nums2 = [1,2,3,4].
输出:[3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 2 ,用加粗斜体标识,nums2 = [1,2,3,4]。下一个更大元素是 3 。
- 4 ,用加粗斜体标识,nums2 = [1,2,3,4]。不存在下一个更大元素,所以答案是 -1 。
提示:
1 <= nums1.length <= nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 10^4
nums1和nums2中所有整数 互不相同
nums1 中的所有整数同样出现在 nums2 中
代码实现
/**
* https://leetcode-cn.com/problems/next-greater-element-i/
*
* @author xiexu
* @create 2022-04-15 23:05
*/
public class _496_下一个更大元素_I {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
Stack<Integer> stack = new Stack<>();
int[] result = new int[nums1.length];
Arrays.fill(result, -1);
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums1.length; i++) {
map.put(nums1[i], i);
}
stack.add(0);
for (int i = 1; i < nums2.length; i++) {
if (nums2[i] < nums2[stack.peek()]) {
stack.push(i);
} else if (nums2[i] == nums2[stack.peek()]) {
stack.push(i);
} else {
while (!stack.isEmpty() && nums2[i] > nums2[stack.peek()]) {
if (map.containsKey(nums2[stack.peek()])) {
Integer index = map.get(nums2[stack.peek()]);
result[index] = nums2[i];
}
stack.pop();
}
stack.push(i);
}
}
return result;
}
}
力扣题目链接
题目
给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。
数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。
示例 1:
输入: nums = [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
示例 2:
输入: nums = [1,2,3,4,3]
输出: [2,3,4,-1,4]
提示:
1 <= nums.length <= 10^4
-10^9 <= nums[i] <= 10^9
代码实现
/**
* https://leetcode-cn.com/problems/next-greater-element-ii/
*
* @author xiexu
* @create 2022-04-15 23:27
*/
public class _503_下一个更大元素_II {
public int[] nextGreaterElements(int[] nums) {
// 边界判断
if (nums == null || nums.length <= 1) {
return new int[]{-1};
}
int size = nums.length;
// 存放结果数组
int[] result = new int[size];
// 默认全部初始化为 -1
Arrays.fill(result, -1);
// 栈中存放的是nums中的元素下标
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < 2 * size; i++) {
while (!stack.isEmpty() && nums[i % size] > nums[stack.peek()]) {
result[stack.peek()] = nums[i % size];
stack.pop();
}
stack.push(i % size);
}
return result;
}
}
力扣题目链接
题目
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5]
输出:9
提示:
n == height.length
1 <= n <= 2 * 10^4
0 <= height[i] <= 10^5
思路
例如求列4的雨水高度,如图:
列4 左侧最高的柱子是列3,高度为2(以下用lHeight
表示)。
列4 右侧最高的柱子是列7,高度为3(以下用rHeight
表示)。
列4 柱子的高度为1(以下用height
表示)
那么列4的雨水高度为 列3和列7的高度最小值减列4的高度,即: min(lHeight, rHeight) - height
。
列4的雨水高度求出来了,宽度为1,相乘就是列4的雨水体积了(1 * 1 = 1)
此时求出了列4的雨水体积。
一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。
代码实现
/**
* https://leetcode-cn.com/problems/trapping-rain-water/
*
* @author xiexu
* @create 2022-04-17 00:03
*/
public class _42_接雨水_双指针 {
public int trap(int[] height) {
int sum = 0;
for (int i = 0; i < height.length; i++) {
// 第一个柱子和最后一个柱子不接雨水
if (i == 0 || i == height.length - 1) {
continue;
}
// 记录右边柱子的最高高度
int rHeight = height[i];
// 记录左边柱子的最高高度
int lHeight = height[i];
for (int r = i + 1; r < height.length; r++) {
if (height[r] > rHeight) {
rHeight = height[r];
}
}
for (int l = i - 1; l >= 0; l--) {
if (height[l] > lHeight) {
lHeight = height[l];
}
}
int h = Math.min(lHeight, rHeight) - height[i];
if (h > 0) {
sum += h;
}
}
return sum;
}
}
思路
从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大
的顺序。
因为一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。
如图:
遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。
例如 5 5 1 3 这种情况。如果添加第二个5的时候就应该将第一个5的下标弹出,把第二个5添加到栈中。
因为我们要求宽度的时候 如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度。
如图所示:
通过 长 * 宽
来计算雨水面积的。
长就是通过柱子的高度来计算,宽是通过柱子之间的下标来计算,
栈里就存放 int 类型的元素就行了,表示下标,想要知道对应的高度,通过height[stack.top()]
就知道弹出的下标对应的高度了。
单调栈处理逻辑
先将下标0的柱子加入到栈中,st.push(0);
。
然后开始从下标1开始遍历所有的柱子,for (int i = 1; i < height.size(); i++)
。
如果当前遍历的元素(柱子)高度小于栈顶元素的高度,就把这个元素加入栈中,因为栈里本来就要保持从小到大的顺序(从栈头到栈底)。
代码如下:
if (height[index] < height[stack.peek()]) {
stack.push(index);
}
如果当前遍历的元素(柱子)高度等于栈顶元素的高度,要跟更新栈顶元素,因为遇到相相同高度的柱子,需要使用最右边的柱子来计算宽度。
代码如下:
if (height[index] == height[stack.peek()]) {
stack.pop();
stack.push(index);
}
如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了,如图所示:
取栈顶元素,将栈顶元素弹出,这个就是凹槽的底部
,也就是中间位置,下标记为mid,对应的高度为 height[mid]
(就是图中的高度1
)。
此时的栈顶元素st.top()
,就是凹槽的左边
位置,下标为 st.top()
,对应的高度为height[st.top()]
(就是图中的高度2
)。
当前遍历的元素i,就是凹槽右边
的位置,下标为i,对应的高度为height[i]
(就是图中的高度3)。
此时大家应该可以发现其实就是栈顶和栈顶的下一个元素以及要入栈的三个元素来接水!
那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:int h = min(height[st.top()], height[i]) - height[mid];
雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度),代码为:int w = i - st.top() - 1 ;
当前凹槽雨水的体积就是:h * w
。
代码实现
/**
* https://leetcode-cn.com/problems/trapping-rain-water/
*
* @author xiexu
* @create 2022-04-17 00:03
*/
public class _42_接雨水_单调栈 {
public int trap(int[] height) {
int size = height.length;
if (size <= 2) {
return 0;
}
Stack<Integer> stack = new Stack<>();
stack.push(0);
int sum = 0;
for (int index = 1; index < size; index++) {
if (height[index] < height[stack.peek()]) { // 情况一
stack.push(index);
} else if (height[index] == height[stack.peek()]) { // 情况二
stack.pop();
stack.push(index);
} else { // 情况三
while (!stack.isEmpty() && (height[index] > height[stack.peek()])) { // 注意这里是while,持续更新栈顶元素
int mid = stack.pop(); // 凹槽底部
if (!stack.isEmpty()) {
int left = stack.peek(); // 凹槽左边
int h = Math.min(height[left], height[index]) - height[mid];
int w = index - left - 1; // 注意减一,只求中间宽度
int hold = h * w;
if (hold > 0) {
sum += hold;
}
}
}
stack.push(index);
}
}
return sum;
}
}
力扣题目链接
题目
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10
示例 2:
输入: heights = [2,4]
输出: 4
提示:
1 <= heights.length <=10^5
0 <= heights[i] <= 10^4
代码实现
/**
* https://leetcode-cn.com/problems/largest-rectangle-in-histogram/
*
* @author xiexu
* @create 2022-04-17 22:21
*/
public class _84_柱状图中最大的矩形 {
public int largestRectangleArea(int[] heights) {
Stack<Integer> stack = new Stack<>();
// 数组扩容,在头和尾各加入一个元素
int[] newHeights = new int[heights.length + 2];
newHeights[0] = 0;
newHeights[newHeights.length - 1] = 0;
for (int i = 0; i < heights.length; i++) {
newHeights[i + 1] = heights[i];
}
heights = newHeights;
stack.push(0);
int result = 0;
// 第一个元素已经入栈,从下标1开始
for (int i = 1; i < heights.length; i++) {
if (heights[i] > heights[stack.peek()]) {
stack.push(i);
} else if (heights[i] == heights[stack.peek()]) {
stack.pop();
stack.push(i);
} else {
while (!stack.isEmpty() && (heights[i] < heights[stack.peek()])) { // 注意是while
int mid = stack.pop();
int left = stack.peek();
int right = i;
int w = right - left - 1;
int h = heights[mid];
result = Math.max(result, w * h);
}
stack.push(i);
}
}
return result;
}
}