出处:代码随想录 代码随想录 代码随想录 代码随想录
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。
每日温度
/**
* leetcode 739. 每日温度
* 单调栈,使用空间换时间,因为遍历的过程中要用一个栈来记录右边第一个比当前元素大的元素,优点是只需要遍历一遍
*
* 需要明确:
* 【单调栈里存放的元素】:存放下标足够
* 【单调栈里是递增还是递减】:顺序为从栈头到栈底(从上到下),递增顺序。
* 只有递增的时候也就是从栈底到栈顶逐渐减小,才知道加入一个元素i时,
* 栈顶元素的数组中右面第一个比栈顶元素大的元素是i
* 【三个判断条件】:T[i] < T[stack.top()]
* T[i] = T[stack.top()]
* T[i] > T[stack.top()]
*
*/
public static int[] dailyTemperatures(int[] temperatures) {
Stack<Integer> stack = new Stack<>();
int[] res = new int[temperatures.length];
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.empty() && temperatures[i] > temperatures[stack.peek()]) {
res[stack.peek()] = i - stack.peek();
stack.pop();
}
stack.push(i);
}
}
return res;
}
/**
* 优化写法
*/
public static int[] dailyTempsOptimized(int[] temperatures) {
Stack<Integer> stack = new Stack<>();
int[] res = new int[temperatures.length];
stack.push(0);
for (int i = 0; i < temperatures.length; i++) {
while (!stack.empty() && temperatures[i] > temperatures[stack.peek()]) {
res[stack.peek()] = i - stack.peek();
stack.pop();
}
stack.push(i);
}
return res;
}
下一个更大元素I
/**
* 下一个更大元素I
* 给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
* 请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。
* nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
* 输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
* 输出: [-1,3,-1]
*
* 【result】要和nums1一样大,并初始化每个元素为-1
* 【单增】:从栈顶到栈底要单增,才能找到右边第一个比自己大的数,遍历到小于等于的元素的时候都要直接push进栈
* 大于的时候如果入栈就不满足栈里单增了,就是找到了右边第一个比自己大的数的时候
*/
public static int[] nextGreaterElement(int[] nums1, int[] nums2) {
Stack<Integer> temp = new Stack<>();
int[] res = Arrays.stream(new int[nums1.length]).map(a -> a = -1).toArray();
HashMap<Integer, Integer> hashMap = new HashMap<>();
// 因为nums1中元素不重复,所以可以用map快速获取值对应的index
for (int i = 0; i < nums1.length; i++) {
// key是数组中的值,value是index
hashMap.put(nums1[i], i);
}
temp.push(0);
for (int i = 1; i < nums2.length; i++) {
if (nums2[i] <= nums2[temp.peek()]) {
// 当前遍历的小于栈顶元素的时候就直接push进去
temp.push(i);
} else {
while (!temp.empty() && nums2[temp.peek()] < nums2[i]) {
if (hashMap.containsKey(nums2[temp.peek()])) {
Integer index = hashMap.get(nums2[temp.peek()]);
// 这次直接要求输出最大的元素
res[index] = nums2[i];
}
temp.pop();
}
temp.push(i);
}
}
return res;
}
下一个更大元素II(循环数组)
/**
* 下一个更大元素II
* 给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。
* 数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。
* 如果不存在,则输出 -1。
*
* 输入: [1,2,1]
* 输出: [2,-1,2]
*/
public static int[] nextGreaterElementsII(int[] nums) {
if (nums == null || nums.length <= 1) {
return new int[]{-1};
}
int size = nums.length;
int[] result = new int[size];
Arrays.fill(result, -1);
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < 2 * size; i++) {
while (!stack.empty() && nums[i % size] > nums[stack.peek()]) {
result[stack.peek()] = nums[i % size];
stack.pop();
}
stack.push(i % size);
}
return result;
}
接雨水
/**
* 接雨水
* 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水
*
* 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
* 输出:6
*
* 【单增还是单减】:从栈顶到栈底单调增,栈顶保存当前最小的
* 因为一旦遍历到比栈顶元素大的,添加的柱子高度大于栈头元素了,就是出现凹槽了,
* 栈头元素为凹槽底部的柱子,栈头第二个元素就是左边的柱子,添加的元素就是凹槽右边的元素
* 【遇到相同柱子高度】:更新栈内下标,弹出旧下标,新下标入栈,因为如果高度相同,后面继续遍历到凹槽要用最右边的柱子计算
* 【遍历逻辑】:如果当前遍历的元素(柱子)高度等于栈顶元素的高度,要跟更新栈顶元素,
* 因为遇到相相同高度的柱子,需要使用最右边的柱子来计算宽度
* 【接水计算】:栈顶和栈顶的下一个元素以及要入栈的三个元素来接水
* h = min(height[st.top()], height[i]) - height[mid]
* w = i - st.top() - 1
* v = h * w
*/
public static 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 i = 1; i < size; i++) {
int stackTop = stack.peek();
if (height[i] < height[stackTop]) {
stack.push(i);
} else if (height[i] == height[stackTop]) {
stack.pop();
stack.push(i);
} else {
while (!stack.empty() && height[i] > height[stackTop]) {
// 找到一个凹槽
int mid = stack.pop();
if (!stack.empty()) {
int left = stack.peek();
int h = Math.min(left, height[i]) - height[mid];
int w = i - left - 1;
int hold = h * w;
if (hold > 0) {
sum += hold;
}
// 一直遍历直到当前height value不比栈头小
stackTop = stack.peek();
}
}
stack.push(i);
}
}
return sum;
}
柱状图中最大的矩形
/**
* 柱状图中最大的矩形
* 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
* 求在该柱状图中,能够勾勒出来的矩形的最大面积。
*
* 【题目如何转化呢】:接雨水是找每个柱子左右两边第一个大于的
* 这道题是找每个柱子左右两边第一个小于的
* 【单调栈里的顺序】:那就应该是从栈顶到栈底递减了
*/
public static 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;
for (int i = 1; i < heights.length; i++) {
if (heights[i] >= heights[stack.peek()]) {
// 要找小的,大于的push
stack.push(i);
} else {
while (heights[i] < heights[stack.peek()]) {
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;
}