文章目录
day60学习内容
day60主要内容
- 柱状图中的最大矩形
声明
本文思路和文字,引用自《代码随想录》
一、柱状图中的最大矩形
84.原题链接
本题可以用双指针法和单调栈法解决。
1.1、双指针法
1.1.1、思路
对于每个条形,使用两个指针分别向左和向右移动,以确定可以延伸的最远距离,直到遇到更矮的条形。
- 循环每个条形:对于直方图中的每个条形,都单独计算以它为高的可能的最大矩形面积。
- 向左扩展:对于当前条形,向左移动指针,直到找到第一个比当前条形低的条形,记录左边界。
- 向右扩展:类似地,向右移动指针,直到找到第一个比当前条形低的条形,记录右边界。
- 计算面积:使用当前条形的高度与计算出的宽度(右边界减左边界加一)乘积得到面积,并更新最大面积。
1.1.2、代码–提交超时,过不了
class Solution {
public int largestRectangleArea(int[] heights) {
int maxArea = 0; // 用于记录找到的最大面积
int n = heights.length; // 直方图的条形数量
// 遍历每个条形,计算以当前条形为高的最大矩形面积
for (int i = 0; i < n; i++) {
int height = heights[i]; // 当前条形的高度
// 向左扩展,直到遇到更矮的条形
int left = i;
while (left > 0 && heights[left - 1] >= height) {
left--;
}
// 向右扩展,直到遇到更矮的条形
int right = i;
while (right < n - 1 && heights[right + 1] >= height) {
right++;
}
// 计算当前条形可以扩展的最大面积
int width = right - left + 1; // 计算宽度
maxArea = Math.max(maxArea, height * width); // 更新最大面积
}
return maxArea; // 返回找到的最大面积
}
}
1.1.3、代码–优化版本,可以提交成功
1、变量初始化
maxArea
:用来记录和更新找到的最大矩形面积。n
:直方图中条形的数量。left
和right
:分别用于存储每个条形的左边界和右边界。
2、计算左边界 (left
数组)
- 对于数组中的每个条形,向左检查所有的条形以确定当前条形的左边界。如果左边的条形高度大于或等于当前条形的高度,那么它不能作为左边界。
- 使用一个指针
p
从当前位置向左移动,如果遇到的条形高度不小于当前条形的高度,那么继续向左移动,直到找到一个较低的条形或到达数组的开始。 - 使用
left[p]
直接跳跃到已知的边界位置,这是一个优化步骤,可以减少不必要的遍历。
3、计算右边界 (right
数组)
- 与计算左边界类似,但是从直方图的右侧开始,向右检查所有的条形以确定当前条形的右边界。
- 使用一个指针
p
从当前位置向右移动,如果遇到的条形高度不小于当前条形的高度,那么继续向右移动,直到找到一个较低的条形或到达数组的末端。 - 同样使用
right[p]
进行跳跃,这有助于快速找到右边界。
4、计算最大矩形面积
- 遍历每个条形,使用预处理好的
left
和right
数组,计算以当前条形为高的矩形的最大宽度。 - 宽度计算为
right[i] - left[i] + 1
(即右边界索引减去左边界索引再加1)。 - 计算当前条形的面积,与之前记录的最大面积比较,更新
maxArea
。
5、返回结果
- 返回计算得到的最大矩形面积
maxArea
。
class Solution {
public int largestRectangleArea(int[] heights) {
int maxArea = 0; // 用来存储和更新找到的最大矩形面积
int n = heights.length; // 直方图中条形的数量
int[] left = new int[n]; // 存储每个条形的左边界
int[] right = new int[n]; // 存储每个条形的右边界
// 初始化左边界
left[0] = 0; // 第一个条形的左边界是它自己,因为左边没有其他条形
for (int i = 1; i < n; i++) {
int p = i - 1; // 从当前条形的前一个条形开始检查
while (p >= 0 && heights[p] >= heights[i]) {
p = left[p] - 1; // 使用已经计算的left数组进行“跳跃”,避免重复遍历
}
left[i] = p + 1; // 确定当前条形的左边界
}
// 初始化右边界
right[n - 1] = n - 1; // 最后一个条形的右边界是它自己,因为右边没有其他条形
for (int i = n - 2; i >= 0; i--) {
int p = i + 1; // 从当前条形的后一个条形开始检查
while (p < n && heights[p] >= heights[i]) {
p = right[p] + 1; // 使用已经计算的right数组进行“跳跃”,避免重复遍历
}
right[i] = p - 1; // 确定当前条形的右边界
}
// 计算最大面积
for (int i = 0; i < n; i++) {
// 计算以当前条形为高的最大矩形面积
maxArea = Math.max(maxArea, heights[i] * (right[i] - left[i] + 1));
}
return maxArea; // 返回计算得到的最大面积
}
}
1.2、单调栈法
1.2.1、思路
-
初始化一个辅助的直方图:在原始的高度数组的两侧添加高度为0的条形,这样做的目的是确保所有原始的条形都能在处理时找到左右边界。
-
遍历直方图:从左到右遍历扩展后的直方图,利用栈来记录可能的左边界。
-
使用栈处理每个条形:对于每个条形,如果它的高度大于栈顶的条形,就将它的索引入栈。如果遇到的条形高度小于栈顶的条形,说明已经找到了栈顶条形的右边界,这时可以弹出栈顶,计算以栈顶条形为高度的矩形面积,直到栈顶的条形高度不再大于当前条形。
-
计算面积:在找到左右边界后,可以计算当前条形能形成的最大矩形的面积,更新最大面积的记录。
-
返回结果:遍历完成后,返回记录的最大面积值。
1.2.2、代码
class Solution {
int largestRectangleArea(int[] heights) {
Stack<Integer> st = new Stack<Integer>();
// 扩展原始高度数组,添加0作为边界,以便处理边缘条形
int [] newHeights = new int[heights.length + 2];
newHeights[0] = 0;
newHeights[newHeights.length - 1] = 0;
for (int index = 0; index < heights.length; index++){
newHeights[index + 1] = heights[index];
}
// 更新高度数组,使用新的包含边界的数组
heights = newHeights;
// 初始化栈,并压入第一个元素的索引,这里是0(高度为0的边界条形)
st.push(0);
int result = 0; // 用于存储最大面积
// 遍历所有的条形
for (int i = 1; i < heights.length; i++) {
// 如果当前条形高度大于栈顶条形高度,直接入栈
if (heights[i] > heights[st.peek()]) {
st.push(i);
}
// 如果当前条形高度等于栈顶条形高度,替换栈顶(这样可以保持最新的索引)
else if (heights[i] == heights[st.peek()]) {
st.pop();
st.push(i);
}
// 如果当前条形高度小于栈顶条形高度,计算可能的最大面积
else {
while (!st.isEmpty() && heights[i] < heights[st.peek()]) {
int mid = st.peek(); // 弹出的栈顶元素,这是当前要计算面积的条形的高度
st.pop(); // 弹出栈顶元素
int left = st.peek(); // 新的栈顶元素是当前弹出元素的左边界
int right = i; // 当前索引是右边界
int w = right - left - 1; // 计算宽度
int h = heights[mid]; // 条形的高度
result = Math.max(result, w * h); // 更新最大面积
}
// 处理完所有右边界后,将当前条形入栈
st.push(i);
}
}
return result; // 返回最大面积
}
}
1.2.2.1、为什么首尾要加0
-
确保所有条形都能找到边界:
- 当你使用单调栈来计算每个条形的最大矩形面积时,需要确定每个条形的左右边界。具体地说,左边界是栈中当前条形左边的第一个比它低的条形,右边界是第一个比它低的右侧条形。
- 通过在数组首尾各添加一个高度为0的条形,可以保证所有原始直方图中的条形在栈操作中自然地找到其边界。例如,左边界的计算不会因为栈为空而导致错误,因为最左边的0始终在栈底;同理,最右边的0条形则自然成为所有条形的右边界。
-
自动处理栈中剩余元素:
- 在遍历结束时,栈中可能还剩余一些条形的索引,这些条形的右边界就是数组的末尾(最后一个0的位置)。添加了0之后,算法可以继续运行,并自动地处理这些剩余条形的面积计算,而无需编写额外的代码来特别处理它们。
- 这意味着在整个数组的遍历过程中,每个条形都会被考虑到,并且当它从栈中弹出时,可以直接使用当前遍历到的位置作为其右边界。
总结
1.感想
- 60天完结撒花。。
2.思维导图
本文思路引用自代码随想录,感谢代码随想录作者。