Day48–单调栈–739. 每日温度,496. 下一个更大元素 I,503. 下一个更大元素 II
单调栈理论基础
摘自《代码随想录》:
那有同学就问了,我怎么能想到用单调栈呢? 什么时候用单调栈呢?
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。
单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。
更直白来说,就是用一个栈来记录我们遍历过的元素,
739. 每日温度
方法:单调栈
思路:
- 单调栈里存下标(保证单调性);这里栈顶到栈底是递增排序。例如
栈底[5,4,3<-栈顶
- 处理入栈的三个情况:
- 情况一:当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
- 直接入栈
- 情况二:当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
- 入栈
- 情况三:当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况
- 出栈,计算,入栈当前i。
- 情况一:当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int[] answer = new int[temperatures.length];
Deque<Integer> stack = new ArrayDeque<>();
// 题目说明数组长度最少为1
// 第一个元素下标入栈
stack.push(0);
for (int i = 1; i < temperatures.length; i++) {
// 情况一、二
if (temperatures[i] <= temperatures[stack.peek()]) {
stack.push(i);
continue;
} else {
// 情况三
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
int topIndex = stack.pop();
answer[topIndex] = i - topIndex;
}
stack.push(i);
}
}
// 栈里还有内容的话,证明右边没有比它大的元素了,赋值0
while (!stack.isEmpty()) {
answer[stack.pop()] = 0;
}
return answer;
}
}
496. 下一个更大元素 I
方法:单调栈
思路:
自己做的。
- 定义一个bigger集合,存放nums2中“比当前值大的,后面元素的值”
- 求bigger集合:和《每日温度》一样的思路方法。区别是,不再存索引的距离,而是直接存值
- 因为nums1是nums2的子集,直接遍历nums1
// 单调栈
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
// 结果集
int[] res = new int[nums1.length];
// bigger集合,打算存比当前值大的,后面元素的值
// bigger索引是nums2[i]的值,值是“下一个比nums2[i]更大的值”
// 等于是当做一个map用
int[] bigger = getBigger(nums2);
// 因为nums1是nums2的子集,直接遍历
for (int i = 0; i < nums1.length; i++) {
res[i] = bigger[nums1[i]];
}
return res;
}
// 和《每日温度》一样的思路方法
private int[] getBigger(int[] nums2) {
// bigger索引是nums2[i]的值,值是“下一个比nums2[i]更大的值”
// 等于是当做一个map用
int[] bigger = new int[10001];
Deque<Integer> stack = new ArrayDeque<>();
// 题目说明数组长度最少为1
// 第一个元素下标入栈
stack.push(0);
for (int i = 1; i < nums2.length; i++) {
// 情况一、二
if (nums2[i] <= nums2[stack.peek()]) {
stack.push(i);
continue;
} else {
// 情况三
while (!stack.isEmpty() && nums2[i] > nums2[stack.peek()]) {
int topIndex = stack.pop();
// 与《每日温度》不同,这里存的是值
bigger[nums2[topIndex]] = nums2[i];
}
stack.push(i);
}
}
while (!stack.isEmpty()) {
// 与《每日温度》不同,找不到的话存的是-1
bigger[nums2[stack.pop()]] = -1;
}
return bigger;
}
}
方法:单调栈(HashMap映射)
思路:
代码引自《代码随想录》
与我的bigger作为map,从nums2映射到nums1不同,它的hashmap是从map1映射到map2
这样比开一个10000长度的数组更省空间
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
HashMap<Integer, Integer> map = new HashMap<>();
// 以值为key,索引为value
for (int i = 0; i < nums1.length; i++) {
map.put(nums1[i], i);
}
int[] res = new int[nums1.length];
Stack<Integer> stack = new Stack<>();
Arrays.fill(res, -1);
for (int i = 0; i < nums2.length; i++) {
while (!stack.isEmpty() && nums2[stack.peek()] < nums2[i]) {
int pre = nums2[stack.pop()];
// 通过值找到,对应在nums1的索引
// nums1存在这个元素的话,赋值。
if (map.containsKey(pre)) {
res[map.get(pre)] = nums2[i];
}
}
stack.push(i);
}
return res;
}
}
503. 下一个更大元素 II
记录:自己写的时候,已经想到要取模操作了,但是写得乱七八糟。题解怎么可以写得这么简单简洁?
方法:单调栈
思路:
和《每日温度》一样。就是变成了“循环数组”,多了个取模操作
// 单调栈,和《每日温度》一样。就是变成了“循环数组”,多了个取模操作
class Solution {
public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[] res = new int[n];
Arrays.fill(res, -1);
//栈中存放的是nums中的元素下标
Deque<Integer> stack = new ArrayDeque<>();
stack.push(0);
for (int i = 1; i < 2 * n; i++) {
// 情况一、二。小于等于
if (nums[i % n] <= nums[stack.peek()]) {
stack.push(i % n);
} else {
// 情况三,大于
while (!stack.isEmpty() && nums[i % n] > nums[stack.peek()]) {
res[stack.peek()] = nums[i % n];
stack.pop();
}
stack.push(i % n);
}
}
return res;
}
}
思路:
其实情况一、二,和三是可以合起来的,因为一、二就是个入栈操作,三最后也是要入栈的。只是都写在一起之后,没有思路分析时候的那么清晰。
// 单调栈,和《每日温度》一样。就是变成了“循环数组”,多了个取模操作
class Solution {
public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[] res = new int[n];
Arrays.fill(res, -1);
//栈中存放的是nums中的元素下标
Deque<Integer> stack = new ArrayDeque<>();
stack.push(0);
for (int i = 1; i < 2 * n; i++) {
// 情况三,大于
while (!stack.isEmpty() && nums[i % n] > nums[stack.peek()]) {
res[stack.peek()] = nums[i % n];
stack.pop();
}
// 情况一、二、三都要入栈
stack.push(i % n);
}
return res;
}
}