Day55_20250203_单调栈part2_42.接雨水|84.柱状图中最大的矩形
42.接雨水
题目
【单调栈应用高频题目,在面试中建议掌握双指针和单调栈】
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5] 输出:9
提示:
n == height.length
1 <= n <= 2 * 10<sup>4</sup>
0 <= height[i] <= 10<sup>5</sup>
思路
- 思路
- 单调递减栈,栈里存的是柱子的索引,保证栈内元素的高度是单调递减的。
- 遍历height数组
- 当前元素<=栈顶元素, 递减的,直接入栈
- 当前元素>栈顶元素,形成洼地,计算积水
- pop栈顶,作为bottom
- 判断栈是否为空
- 如果为空,break
- 如果不为空,计算左右两边围成的水池
- 宽度,int width = i - left - 1;
- 高度,int heightWater = Math.min(height[left], height[i]) - height[bottom];
- 更新总积水量:water += width * heightWater; // 更新总积水量
- 继续遍历,入栈i,确保栈中存储的柱子始终递减
- 代码
class Solution { public int trap(int[] height) { if (height == null || height.length < 3) return 0; // 边界情况 Stack<Integer> stack = new Stack<>(); // 单调递减栈,存储柱子的索引 int water = 0; for (int i = 0; i < height.length; i++) { //栈不为空,且当前遍历元素>栈顶元素,有可能形成洼地,需要计算积水。 while (!stack.isEmpty() && height[i] > height[stack.peek()]) { int bottom = stack.pop(); // pop栈顶,作为低洼处的索引 if (stack.isEmpty()) break; // 栈空了,说明左侧没有柱子了,无法存水 //如果栈不为空,计算左右两边围成的水池 int left = stack.peek(); // 左侧柱子的索引,仅仅获取,不弹出 // 宽度:i为低洼处右边的高处的元素-左-1 int width = i - left - 1; // 高度:两边的最低处-低洼处 int heightWater = Math.min(height[left], height[i]) - height[bottom]; water += width * heightWater; // 更新总积水量 } //当前遍历元素<=栈顶元素,单调递减,直接入栈 stack.push(i); // 当前柱子入栈 } return water; } }
总结
- 难题,难点在于怎么计算高度和宽度的代码思路。
84.柱状图中最大的矩形
题目
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
输入:heights = [2,1,5,6,2,3] 输出:10 解释:最大的矩形为图中红色区域,面积为 10
示例 2:
输入: heights = [2,4] 输出: 4
提示:
1 <= heights.length <=10<sup>5</sup>
0 <= heights[i] <= 10<sup>4</sup>
思路
- 思路
- 单调递减栈
- 遍历newHeight数组,
- 当前元素>=栈顶元素,直接入栈
- 当前元素<栈顶元素,以栈顶为基准,计算按照栈顶高度形成的面积。
- 继续入栈stack.push(i);
- 代码
class Solution { public int largestRectangleArea(int[] heights) { int[] newHeight=new int[heights.length+2]; //将heights数组复制到newHeight数组中 System.arraycopy(heights,0,newHeight,1,heights.length); //扩展数组 首尾各扩展1个0 newHeight[heights.length+1]=0; newHeight[0]=0; //单调递减栈 Stack<Integer> stack=new Stack<>(); stack.push(0);//放入第一个元素 int res=0;//存储结果 for(int i=1;i<newHeight.length;i++){ //当前元素<栈顶元素 while(newHeight[i]<newHeight[stack.peek()]){ int mid=stack.pop();//pop栈顶,作为基准 int left=stack.peek(); int w=i-left-1;//宽度:右-左-1 int h=newHeight[mid];//高度:以mid为基准 res=Math.max(res,w*h);//更新最终结果 } stack.push(i);//继续入栈 } return res; } }
总结
- 为什么要在首尾各扩展1个0【扩展数组】?
- 保证栈中的每个元素最终都能计算出面积。例如递增数组[8,6,4,2],在8入栈后,6与8比较,得到mid(8),right(6),但得不到left。
- 避免特殊情况。当栈非空时没有更多的柱子可以比较,加入0统一处理这个问题。