Day48-单调栈,leetcode739,496,503
单调栈
- 通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。时间复杂度为O(n)。
- 单调栈的作用,用来存放/记录遍历过的元素,将之前的元素以某种方式存放起来了,存放元素的下标i,与当前元素做对比,这样才知道当前元素是不是比之前遍历过的某个元素大,然后再进行一个求值的过程。从栈头到栈底的顺序,递增,因为只有递增的时候,栈里要加入一个元素i的时候,才知道栈顶元素在数组中右面第一个比栈顶元素大的元素是i。即:如果求一个元素右边第一个更大元素,单调栈就是递增的,如果求一个元素右边第一个更小元素,单调栈就是递减的
- 每日温度
- 给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
- 思路
- 单调栈的作用,用来存放/记录遍历过的元素,将之前的元素以某种方式存放起来了,存放元素的下标i,与当前元素做对比,这样才知道当前元素是不是比之前遍历过的某个元素大,然后再进行一个求值的过程。从栈头到栈底的顺序,递增,因为只有递增的时候,栈里要加入一个元素i的时候,才知道栈顶元素在数组中右面第一个比栈顶元素大的元素是i。即:如果求一个元素右边第一个更大元素,单调栈就是递增的,如果求一个元素右边第一个更小元素,单调栈就是递减的
- result,存放结果,元素下标为栈顶的下标,计算元素对比的下标差值
- 当前元素 小于 栈顶元素,将当前元素推人栈中;当前元素 等于 栈顶元素,将当前元素推入栈中;当前元素 大于 栈顶元素,记录结果到result,result记录的是栈顶的下标,记录距离就是当前遍历的i和栈顶的下标的位置相减就是要求的距离,栈顶元素用过了就将其弹出,最终的结果就存在result中
/**
* 1. 变量说明
res:结果数组,初始化为全 0,存储每一天距离下一个更高温度的天数。
stack:单调递增栈,存储温度数组的下标。
* 2. 算法流程
首先把第 0 天的下标压入栈。
从第 1 天开始遍历温度数组:
如果当前温度小于或等于栈顶温度,下标直接入栈。
如果当前温度大于栈顶温度,说明找到了栈顶下标对应的“下一个更高温度”,弹出栈顶并计算距离,直到栈为空或栈顶温度不小于当前温度。
每次遍历后都把当前下标入栈。
* 3. 结果计算
当遇到更高温度时,res[top] = i - top,表示第 top 天距离第 i 天的天数。
没有更高温度的天数保持为 0。
* 用单调递增栈存储下标,遇到更高温度时弹出并计算距离,最终得到每一天距离下一个更高温度的天数。
*/
var dailyTemperatures = function(temperatures) {
const n = temperatures.length;
const res = Array(n).fill(0);
const stack = []; // 递增栈:用于存储元素右面第一个比他大的元素下标
stack.push(0);
for (let i = 1; i < n; i++) {
// 栈顶元素
const top = stack[stack.length - 1];
if (temperatures[i] < temperatures[top]) {
stack.push(i);
} else if (temperatures[i] === temperatures[top]) {
stack.push(i);
} else {
while (stack.length && temperatures[i] > temperatures[stack[stack.length - 1]]) {
const top = stack.pop();
res[top] = i - top;
}
stack.push(i);
}
}
return res;
};
- 下一个更大元素 I
- 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的每个元素在nums2中下一个比当前元素大的元素,定义一个和nums1一样大小的数组result来存放结果。查找不到是-1,即result数组初始化为-1。在遍历nums2的过程中,判断nums2[i]是否在nums1中出现过,因为最后是要根据nums1元素的下标来更新result数组。
/**
* 1. umap 哈希表
记录 nums1 中每个元素在 nums1 的下标,方便后续直接定位 result 数组。
* 2. 单调栈 stack
存储 nums2 的下标,维护一个递减栈。
* 3. 遍历 nums2
如果当前元素小于或等于栈顶元素,下标直接入栈。
如果当前元素大于栈顶元素,说明找到了栈顶元素的下一个更大元素:
如果栈顶元素在 nums1 中出现过,则更新 result 数组对应位置。
弹出栈顶,继续比较直到栈为空或栈顶元素不小于当前元素。
每次遍历后都把当前下标入栈。
* 4. 返回结果
result 数组即为 nums1 每个元素在 nums2 中的下一个更大元素,没有则为 -1。
* 用单调栈和哈希表,快速找到 nums1 中每个元素在 nums2 中的下一个更大元素。
*/
function nextGreaterElement(nums1, nums2) {
const stack = [];
const result = new Array(nums1.length).fill(-1);
if (nums1.length === 0) return result;
// 哈希表,记录 nums1 中每个元素的下标
const umap = new Map();
for (let i = 0; i < nums1.length; i++) {
umap.set(nums1[i], i);
}
stack.push(0);
for (let i = 1; i < nums2.length; i++) {
if (nums2[i] < nums2[stack[stack.length - 1]]) {
stack.push(i);
} else if (nums2[i] === nums2[stack[stack.length - 1]]) {
stack.push(i);
} else {
while (stack.length && nums2[i] > nums2[stack[stack.length - 1]]) {
const topIdx = stack[stack.length - 1];
if (umap.has(nums2[topIdx])) {
const index = umap.get(nums2[topIdx]);
result[index] = nums2[i];
}
stack.pop();
}
stack.push(i);
}
}
return result;
}
- 下一个更大元素 II
- 给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。
- 数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。
- 思路
- 成环的问题,首尾相连,可以考虑两个相同的数组拼接在一起
[...arr, ...arr],使用单调栈计算出每一个元素的下一个最大值,最后再把结果集即result数组slice到原数组大小就可以了。 - 成环的问题,首尾相连,可以考虑用取模的方式去模拟成环的过程
/**
* 1. 拼接数组
arr = nums.concat(nums),将原数组拼接一遍,模拟循环数组。
* 2. 初始化结果数组
result 长度为拼接后数组长度,初始值为 -1。
* 3. 单调栈处理
遍历拼接后的数组,维护一个递减栈,存放下标。
如果当前元素小于或等于栈顶元素,下标入栈。
如果当前元素大于栈顶元素,说明找到了栈顶元素的下一个更大元素,弹出栈顶并更新结果,直到栈为空或栈顶元素不小于当前元素。
每次遍历后都把当前下标入栈。
* 4. 返回结果
只保留原数组长度的结果,即 result.slice(0, n)。
* 通过拼接数组和单调栈,模拟循环数组,计算每个元素的下一个更大元素,最后返回原数组长度的结果。
*/
function nextGreaterElements(nums) {
// 拼接一个新的 nums,实现循环数组
const n = nums.length;
const arr = nums.concat(nums);
const result = new Array(arr.length).fill(-1);
const stack = [];
stack.push(0);
for (let i = 1; i < arr.length; i++) {
if (arr[i] < arr[stack[stack.length - 1]]) {
stack.push(i);
} else if (arr[i] === arr[stack[stack.length - 1]]) {
stack.push(i);
} else {
while (stack.length && arr[i] > arr[stack[stack.length - 1]]) {
result[stack[stack.length - 1]] = arr[i];
stack.pop();
}
stack.push(i);
}
}
// 只保留原数组长度的结果
return result.slice(0, n);
}
/**
* 1. 初始化结果数组
result 长度为原数组,初始值为 -1。
* 2. 单调栈处理
用 stack 存放下标,维护递减栈。
遍历两遍数组(i < n * 2),用 i % n 实现循环数组效果。
* 3. 遍历逻辑
如果当前元素小于或等于栈顶元素,下标入栈。
如果当前元素大于栈顶元素,说明找到了栈顶元素的下一个更大元素,弹出栈顶并更新结果,直到栈为空或栈顶元素不小于当前元素。
每次遍历后都把当前下标入栈。
* 4. 返回结果
返回 result,每个元素是下一个更大元素,没有则为 -1。
* 通过遍历两遍数组并用取模模拟循环,结合单调栈,计算每个元素的下一个更大元素。
*/
function nextGreaterElements(nums) {
const n = nums.length;
const result = new Array(n).fill(-1);
if (n === 0) return result;
const stack = [];
stack.push(0);
// 遍历两遍数组,模拟循环
for (let i = 1; i < n * 2; i++) {
const idx = i % n;
if (nums[idx] < nums[stack[stack.length - 1]]) {
stack.push(idx);
} else if (nums[idx] === nums[stack[stack.length - 1]]) {
stack.push(idx);
} else {
while (stack.length && nums[idx] > nums[stack[stack.length - 1]]) {
result[stack[stack.length - 1]] = nums[idx];
stack.pop();
}
stack.push(idx);
}
}
return result;
}
参考&感谢各路大神
宝剑锋从磨砺出,梅花香自苦寒来。

浙公网安备 33010602011771号