算法-单调栈
1. 每日温度(LeetCode 739)
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,
其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
思路:
- 单调栈的作用:用于解决寻找右边第一个比当前元素 大/小 的元素
- 单调栈中存放的是数组的下标
- 寻找右边第一个比当前元素大的元素时,从栈底到栈顶为递减序列。
- 时间复杂度:O(n);空间复杂度:O(n)
class Solution {
// 暴力解法
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] answer = new int[n];
// 单调栈,存放的是下标
Stack<Integer> stack = new Stack<Integer>();
// 遍历数组元素
for(int i = 0; i<n; ++i) {
if(stack.empty()) {
stack.push(i);
continue;
}
while(!stack.empty() && temperatures[i] > temperatures[stack.peek()]) {
answer[stack.peek()] = i-stack.peek();
// 弹出栈顶下标
stack.pop();
}
// 下标i入栈
stack.push(i);
}
// 最后留在战中的元素右边,answer默认为0
return answer;
}
}
2. 下一个更大元素 I(LeetCode 496)
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] 是如上所述的 下一个更大元素 。
输入: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 。
思路:
- 因为nums1和nums2有一种乱序的对应关系,并且没有重复元素。所以用map将值和下标建立对应关系,方便填充answer数组。
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
int n1 = nums1.length;
int n2 = nums2.length;
// 存放结果
int[] answer = new int[n1];
Arrays.fill(answer, -1);
// 用一个hashmap存放nums1[i]和i的映射关系
HashMap<Integer, Integer> map = new HashMap<>();
for(int i = 0; i<n1; ++i) {
map.put(nums1[i], i);
}
// 单调栈,存放的是nums2的下标
Stack<Integer> stack = new Stack<>();
for(int i = 0; i<n2; ++i) {
if(stack.empty()) {
stack.push(i);
}
while(!stack.empty() && nums2[i] > nums2[stack.peek()]) {
// 如果栈顶元素在nums1中出现过才需要记录
if(map.containsKey(nums2[stack.peek()])) {
int index1 = map.get(nums2[stack.peek()]);
answer[index1] = nums2[i];
}
stack.pop();
}
stack.push(i);
}
return answer;
}
}
3. 下一个更大元素II(LeetCode 503)
给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。
数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,
这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。
class Solution {
public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[] answer = new int[n];
Arrays.fill(answer, -1);
Stack<Integer> stack = new Stack<>();
// 第一遍遍历
for(int i = 0; i<n; ++i) {
if(stack.empty())
stack.push(i);
while(!stack.empty() && nums[i] > nums[stack.peek()]) {
answer[stack.peek()] = nums[i];
stack.pop();
}
stack.push(i);
}
// 第二遍遍历,不需要完整遍历,遍历到最大元素重复即可
for(int i = 0; i<n; ++i) {
// 最后剩下来的是最大元素
if(stack.size() == 1)
break;
while(!stack.empty() && nums[i] > nums[stack.peek()]) {
answer[stack.peek()] = nums[i];
stack.pop();
}
}
return answer;
}
}
4. 接雨水(LeetCode 42)(有难度)
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
class Solution {
public int trap(int[] height) {
int n = height.length;
int sum = 0;
Stack<Integer> stack = new Stack<>();
stack.push(0);
for(int i = 1;i<n; ++i) {
if(height[i] < height[stack.peek()])
stack.push(i);
// 遇到相同数值的,需要更新下标
else if(height[i] == height[stack.peek()]) {
stack.pop();
stack.push(i);
}
else {
while(!stack.empty() && height[i] > height[stack.peek()]) {
int midIndex = stack.pop();
// 弹出midIndex之后,左边元素 + 右边栈外元素 才能形成凹槽
// 高 低 高 <=> height[stack.peek()], height[midIndex], height[i]
if(!stack.empty()) {
// 按行来计算雨水
int h = Math.min(height[stack.peek()], height[i]) - height[midIndex];
int w = i - stack.peek() - 1;
sum += h * w;
}
}
stack.push(i);
}
}
return sum;
}
}
5. 柱状图中最大的矩形(LeetCode 84)(有难度)
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] newheights = new int[n+2];
// 首部填0,确保栈始终非空
newheights[0] = 0;
// 尾部填0,确保以每个元素为高的矩形都被计算
newheights[n+1] = 0;
for(int i = 0; i<n; ++i)
newheights[i+1] = heights[i];
int result = 0;
// 从栈底到栈顶递增
Stack<Integer> stack = new Stack<>();
stack.push(0);
for(int i = 1; i<n+2; ++i) {
if(newheights[i] >= newheights[stack.peek()])
stack.push(i);
else {
// 矩形的左边界是 leftIndex + 1, 右边界是i-1
while(newheights[i] < newheights[stack.peek()]) {
int h = newheights[stack.pop()];
int leftIndex = stack.peek();
int w = i - leftIndex - 1;
result = Math.max(result, h * w);
}
stack.push(i);
}
}
return result;
}
}