Day49–单调栈–42. 接雨水,84. 柱状图中最大的矩形
42. 接雨水
这里已经是hard题目了,要是没想出来,别硬刚。先看题解,解说。做得出来自己做一遍,做不出来抄一遍日后回来再做。
方法:双指针法
思路:
辅助数组记录左/右边最大高度
class Solution {
public int trap(int[] height) {
int n = height.length;
if (n <= 2) {
return 0;
}
int[] maxLeft = new int[n];
int[] maxRight = new int[n];
// 记录每个柱子左边柱子最大高度
maxLeft[0] = height[0];
for (int i = 1; i < n; i++) {
maxLeft[i] = Math.max(height[i], maxLeft[i - 1]);
}
// 记录每个柱子右边柱子最大高度
maxRight[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) {
maxRight[i] = Math.max(height[i], maxRight[i + 1]);
}
// 求和
int sum = 0;
for (int i = 0; i < n; i++) {
int count = Math.min(maxLeft[i], maxRight[i]) - height[i];
sum += count;
}
return sum;
}
}
方法:双指针法优化
思路:
- 双指针优化:更符合双指针的概念。
- 但是理解起来更烧脑。
- 因为这是“从两边向中间寻找最值”,而不是原来的,“根据当前列,判断当前列的左右两边的最大最大高度。”
- 可以这么解释,比如现在在左边,maxLeft < maxRight了,是时候统计左边列的当前装水量了,为什么当前这个水肯定能被装上?右边一定有堵墙吗?——没错,对,就是右边有堵墙,l一直往右走,一定会遇到r,所以也一定会遇到maxRight
// 双指针优化:更符合双指针的概念。
// 但是理解起来更烧脑。
// 因为这是“从两边向中间寻找最值”,而不是原来的,“根据当前列,判断当前列的左右两边的最大最大高度。”
// 可以这么解释,比如现在在左边,maxLeft < maxRight了,是时候统计左边列的当前装水量了,为什么当前这个水肯定能被装上?右边一定有堵墙吗?——没错,对,就是右边有堵墙,l一直往右走,一定会遇到r,所以也一定会遇到maxRight
class Solution {
public int trap(int[] height) {
if (height.length <= 2) {
return 0;
}
// 从两边向中间寻找最值
int maxLeft = height[0];
int maxRight = height[height.length - 1];
int left = 1;
int right = height.length - 2;
int res = 0;
while (left <= right) {
// 不确定上一轮是左边移动还是右边移动,所以两边都需更新最值
maxLeft = Math.max(maxLeft, height[left]);
maxRight = Math.max(maxRight, height[right]);
// 最值较小的一边所能装的水量已定,所以移动较小的一边。
if (maxLeft < maxRight) {
res += maxLeft - height[left];
left++;
} else {
res += maxRight - height[right];
right--;
}
}
return res;
}
}
方法:单调栈
思路:
寻找右边第一个不单调值,中间就会出现坑
// 单调栈方法:寻找右边第一个不单调值,中间就会出现坑
class Solution {
public int trap(int[] height) {
if (height.length <= 2) {
return 0;
}
// 存着下标,计算的时候用下标对应的柱子高度
Stack<Integer> st = new Stack<>();
int sum = 0;
st.push(0);
for (int i = 1; i < height.length; i++) {
// 情况一(值比栈顶的小)
// 栈的大概情况——栈底[5,4,3栈顶
if (height[i] < height[st.peek()]) {
st.push(i);
}
// 情况二,值相等,只存最新的
if (height[i] == height[st.peek()]) {
st.pop();
st.push(i);
} else {
// 情况三,值比栈顶的大。证明栈顶是“水坑”,可以储水
// 注意这里是while
while (!st.isEmpty() && height[i] > height[st.peek()]) {
// 弹出了一个元素作为mid
int mid = st.pop();
// 此时的高度:栈顶>height[mid],height[mid]<height[i]
// 所以栈里必须还有元素,不然就是边缘的情况,也就是0的位置
if (!st.isEmpty()) {
// 取高度:min(栈顶位置,i位置),“短板效应”
int h = Math.min(height[st.peek()], height[i]) - height[mid];
// 取宽度:注意减一,只求中间宽度
int w = i - st.peek() - 1;
sum += h * w;
}
}
// 经历了while循环之后,此时i位置的柱子,是小于等于栈顶的,入栈
st.push(i);
}
}
return sum;
}
}
84. 柱状图中最大的矩形
记录:这道题,看了题解之后,第二天尝试自己复现,还是不断报错。关于一些边界的处理问题,初始化的问题,要非常小心。
方法:双指针法
思路:
和《接雨水》一样,但是要记录”每个柱子左边第一个小于该柱子的下标“,而不是”左边第一个小于该柱子的高度“
注意:t = minLeftIndex[t];
写成t++
的话,时间会超过限制。不能一个一个跳。
// 双指针法。辅助数组(记录左边/右边第一个小于该柱子的下标)
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] minLeftIndex = new int[n];
int[] minRightIndex = new int[n];
// 注意这里初始化,防止下面while死循环
minLeftIndex[0] = -1;
// 记录每个柱子 左边第一个小于该柱子的下标
for (int i = 1; i < n; i++) {
int t = i - 1;
// 这里不是用if,而是不断向左寻找的过程
while (t >= 0 && heights[t] >= heights[i]) {
// 注意:`t = minLeftIndex[t];`写成`t++`的话,时间会超过限制。不能一个一个跳。
t = minLeftIndex[t];
}
minLeftIndex[i] = t;
}
// 注意这里初始化,防止下面while死循环
minRightIndex[n - 1] = n;
// 记录每个柱子 右边第一个小于该柱子的下标
for (int i = n - 2; i >= 0; i--) {
int t = i + 1;
// 这里不是用if,而是不断向右寻找的过程
while (t < n && heights[t] >= heights[i]) {
t = minRightIndex[t];
}
minRightIndex[i] = t;
}
// 求和
int result = 0;
for (int i = 0; i < n; i++) {
int sum = heights[i] * (minRightIndex[i] - minLeftIndex[i] - 1);
result = Math.max(sum, result);
}
return result;
}
}
方法:单调栈法
思路:
与《接雨水》相比,栈内的单调性相反。
// 单调栈方法。与《接雨水》相比,栈内的单调性相反。
class Solution {
public int largestRectangleArea(int[] heights) {
int res = 0;
// stack存的是下标
Deque<Integer> stack = new ArrayDeque<>();
// 创建新数组,在原数组基础上头部和尾部各加一个0
int[] newHeights = new int[heights.length + 2];
for (int i = 0; i < heights.length; i++) {
newHeights[i + 1] = heights[i];
}
newHeights[0] = 0;
newHeights[newHeights.length - 1] = 0;
stack.push(0);
// 第一个元素已经入栈,从下标1开始
for (int i = 1; i < newHeights.length; i++) {
// 情况一,当前高度大于左边高度
if (newHeights[i] > newHeights[stack.peek()]) {
stack.push(i);
// 情况二,当前高度等于左边高度
} else if (newHeights[i] == newHeights[stack.peek()]) {
stack.pop();
stack.push(i);
} else {
// 情况三,当前高度小于左边高度
// 注意是while
while (!stack.isEmpty() && newHeights[i] < newHeights[stack.peek()]) {
int mid = stack.pop();
if (!stack.isEmpty()) {
int left = stack.peek();
int right = i;
int w = right - left - 1;
int h = newHeights[mid];
res = Math.max(res, w * h);
}
}
stack.push(i);
}
}
return res;
}
}