Day49-单调栈,leetcode42,84
- 接雨水
- 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
-
思路
-
单调栈的顺序总结: 求一个元素右边第一个更大元素,单调栈就是递增的, 求一个元素右边第一个更小元素,单调栈就是递减的。
-
双指针
//双指针
/**
* 1. maxLeft 和 maxRight
maxLeft[i]:表示第 i 个柱子左边(含自身)最高的柱子高度。
maxRight[i]:表示第 i 个柱子右边(含自身)最高的柱子高度。
* 2. 预处理左右最大高度
从左到右遍历,填充 maxLeft,每个位置取当前高度和左边最大值的较大者。
从右到左遍历,填充 maxRight,每个位置取当前高度和右边最大值的较大者。
* 3. 计算每个柱子能接的雨水
对每个柱子,能接的雨水量 = min(maxLeft[i], maxRight[i]) - height[i]。
如果结果大于 0,累加到总和 sum。
* 4. 返回结果
返回所有柱子能接雨水的总量 sum。
* 通过预处理每个柱子左右最大高度,计算每个柱子能接的雨水量,最后累加得到总雨水量。
*/
var trap = function(height) {
const len = height.length;
if(len <= 2) return 0;
const maxLeft = new Array(len).fill(0);
const maxRight = new Array(len).fill(0);
// 记录每个柱子左边柱子最大高度
maxLeft[0] = height[0];
for(let i = 1; i < len; i++){
maxLeft[i] = Math.max(height[i], maxLeft[i - 1]);
}
// 记录每个柱子右边柱子最大高度
maxRight[len - 1] = height[len - 1];
for(let i = len - 2; i >= 0; i--){
maxRight[i] = Math.max(height[i], maxRight[i + 1]);
}
// 求和
let sum = 0;
for(let i = 0; i < len; i++){
let count = Math.min(maxLeft[i], maxRight[i]) - height[i];
if(count > 0) sum += count;
}
return sum;
};
- 单调栈解法
//单调栈 js数组作为栈
/**
* 1. 变量说明
st:单调栈,存放柱子的下标。
sum:累计接到的雨水总量。
* 2. 算法流程
1. 从左到右遍历每个柱子。
2. 如果当前柱子高度小于或等于栈顶柱子高度,下标直接入栈(情况一和情况二)。
如果相等,先弹出再入栈,处理重复高度。
3. 如果当前柱子高度大于栈顶柱子高度(情况三):
说明遇到右边更高的柱子,可以形成“凹槽”接雨水。
用 while 循环弹出栈顶,计算雨水:
mid 是被弹出的柱子下标。
如果栈不为空,说明左边还有柱子,可以形成凹槽。
高度 h = min(左边柱子高度, 当前柱子高度) - mid柱子高度
宽度 w = 当前下标 - 左边柱子下标 - 1
雨水量 h * w 累加到 sum。
当前下标入栈。
* 3. 返回结果
返回累计雨水总量 sum。
* 用单调栈维护柱子下标,遇到右边更高的柱子时,弹出栈顶并计算能接的雨水,最终得到总雨水量。
*/
var trap = function(height) {
const len = height.length;
if(len <= 2) return 0; // 可以不加
const st = [];// 存着下标,计算的时候用下标对应的柱子高度
st.push(0);
let sum = 0;
for(let i = 1; i < len; i++){
if(height[i] < height[st[st.length - 1]]){ // 情况一
st.push(i);
}
if (height[i] == height[st[st.length - 1]]) { // 情况二
st.pop(); // 其实这一句可以不加,效果是一样的,但处理相同的情况的思路却变了。
st.push(i);
} else { // 情况三
while (st.length !== 0 && height[i] > height[st[st.length - 1]]) { // 注意这里是while
let mid = st[st.length - 1];
st.pop();
if (st.length !== 0) {
let h = Math.min(height[st[st.length - 1]], height[i]) - height[mid];
let w = i - st[st.length - 1] - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}
st.push(i);
}
}
return sum;
};
- 柱状图中最大的矩形
- 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
- 求在该柱状图中,能够勾勒出来的矩形的最大面积。
-
思路
-
双指针解法
-
要记录记录每个柱子 左边第一个小于该柱子的下标
//双指针 js中运行速度最快
/**
* 1. 变量说明
minLeftIndex[i]:第 i 个柱子左边第一个比它矮的柱子的下标。
maxRightIndex[i]:第 i 个柱子右边第一个比它矮的柱子的下标。
* 2. 预处理左右边界
左边界:
对每个柱子,从左往右遍历,找到左边第一个比当前柱子矮的位置。
用 while 循环不断向左跳,直到找到比当前柱子矮的柱子,避免重复遍历。
右边界
对每个柱子,从右往左遍历,找到右边第一个比当前柱子矮的位置。
用 while 循环不断向右跳,直到找到比当前柱子矮的柱子。
* 3. 计算最大面积
对每个柱子,以它为高,左右边界之间的宽度为 maxRightIndex[i] - minLeftIndex[i] - 1。
面积为 heights[i] * 宽度,每次取最大值。
* 4. 返回结果
返回所有可能矩形中的最大面积 maxArea。
* 通过预处理每个柱子的左右第一个比它矮的柱子下标,快速计算以每个柱子为高的最大矩形面积,时间复杂度 O(n)。
*/
var largestRectangleArea = function(heights) {
const len = heights.length;
const minLeftIndex = new Array(len);
const maxRightIndex = new Array(len);
// 记录每个柱子 左边第一个小于该柱子的下标
minLeftIndex[0] = -1; // 注意这里初始化,防止下面while死循环
for(let i = 1; i < len; i++) {
let t = i - 1;
// 这里不是用if,而是不断向左寻找的过程
while (t >= 0 && heights[t] >= heights[i]) {
t = minLeftIndex[t];
}
minLeftIndex[i] = t;
}
// 记录每个柱子 右边第一个小于该柱子的下标
maxRightIndex[len - 1] = len; // 注意这里初始化,防止下面while死循环
for(let i = len - 2; i >= 0; i--){
let t = i + 1;
// 这里不是用if,而是不断向右寻找的过程
while (t <= len && heights[t] > heights[i]) {
t = maxRightIndex[t];
}
maxRightIndex[i] = t;
}
// 求和
let maxArea = 0;
for(let i = 0; i < len; i++){
let sum = heights[i] * (maxRightIndex[i] - minLeftIndex[i] - 1);
maxArea = Math.max(maxArea , sum);
}
return maxArea;
};
- 单调栈解法
- 要找每个柱子左右两边第一个小于该柱子的柱子,所以从栈头(元素从栈头弹出)到栈底的顺序应该是从大到小的顺序。只有栈里从大到小的顺序,才能保证栈顶元素找到左右两边第一个小于栈顶元素的柱子。栈顶和栈顶的下一个元素以及要入栈的三个元素组成了我们要求最大面积的高度和宽度.
//单调栈
/**
* 1. 初始化
maxArea: 存储当前找到的最大面积
stack: 单调递增栈,初始放入第一个柱子的索引0
heights.push(0): 在末尾添加高度为0的哨兵柱子,确保所有柱子都能被处理
n: 处理后的柱子数量
* 2. 主循环,三种情况处理:
情况三:当前柱子高度 > 栈顶柱子高度
直接入栈,保持栈的单调递增性
情况二:当前柱子高度 == 栈顶柱子高度
可以弹出再压入(效果相同)
也可以直接压入,目的是保持正确的左边界计算
情况一:当前柱子高度 < 栈顶柱子高度,
弹出栈顶元素,计算以该柱子为高的矩形面积
宽度计算:i - left - 1,其中left是新的栈顶元素
更新最大面积
最后将当前柱子索引压入栈
* 3. 返回结果
返回计算得到的最大矩形面积
*/
var largestRectangleArea = function(heights) {
let maxArea = 0;
const stack = [0];
heights.push(0);
const n = heights.length;
for (let i = 1; i < n; i++) {
// JavaScript 中获取数组最后一个元素的方法,作用是获取栈顶元素的值
let top = stack.at(-1);
// 情况三
if (heights[top] < heights[i]) {
stack.push(i);
}
// 情况二
if (heights[top] === heights[i]) {
stack.pop(); // 这个可以加,可以不加,效果一样,思路不同
stack.push(i);
}
// 情况一
if (heights[top] > heights[i]) {
while (stack.length > 0 && heights[top] > heights[i]) {
// 栈顶元素出栈,并保存栈顶bar的索引
const h = heights[stack.pop()];
const left = stack.at(-1) ?? -1;
const w = i - left - 1;
// 计算面积,并取最大面积
maxArea = Math.max(maxArea, w * h);
top = stack.at(-1);
}
stack.push(i);
}
}
return maxArea;
};
参考&感谢各路大神
宝剑锋从磨砺出,梅花香自苦寒来。