柱状图最大矩形问题是一个经典的算法题目,要求我们在给定的柱状图中找出面积最大的矩形。这个问题看似简单,但蕴含着丰富的算法思想和优化技巧。在实际应用中,这类问题可以用于图像处理、数据可视化等多个领域。
给定一个非负整数数组heights,表示柱状图中各个柱子的高度,每个柱子的宽度为1。我们需要找出这个柱状图中能够勾勒出的最大矩形的面积。例如,对于输入heights = [2,1,5,6,2,3],最大的矩形面积是10(对应高度为5和6的两个柱子组成的矩形)。
三次遍历法的核心思想是:对于每个柱子,找到它左右两侧第一个比它矮的柱子的位置,然后计算以当前柱子高度为高的最大矩形面积。
具体步骤如下:
java复制public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] left = new int[n];
Deque<Integer> st = new ArrayDeque<>();
// 第一次遍历:计算left数组
for(int i = 0; i < n; i++) {
int h = heights[i];
while(!st.isEmpty() && heights[st.peek()] >= h)
st.pop();
left[i] = st.isEmpty() ? -1 : st.peek();
st.push(i);
}
// 第二次遍历:计算right数组
int[] right = new int[n];
st.clear();
for(int i = n-1; i >= 0; i--) {
int h = heights[i];
while(!st.isEmpty() && heights[st.peek()] >= h)
st.pop();
right[i] = st.isEmpty() ? n : st.peek();
st.push(i);
}
// 第三次遍历:计算最大面积
int ret = 0;
for(int i = 0; i < n; i++) {
int area = heights[i] * (right[i] - left[i] - 1);
ret = Math.max(ret, area);
}
return ret;
}
注意:使用单调栈时,栈中存储的是柱子的下标而不是高度值,这样可以同时访问高度和位置信息。
在三次遍历法中,我们发现计算right数组时可以利用计算left数组时的信息。具体来说,当从栈中弹出元素时,当前遍历的位置正好是被弹出元素的右侧第一个更矮柱子的位置。
java复制public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] left = new int[n];
int[] right = new int[n];
Arrays.fill(right, n); // 初始化right数组
Deque<Integer> st = new ArrayDeque<>();
for(int i = 0; i < n; i++) {
int h = heights[i];
while(!st.isEmpty() && heights[st.peek()] >= h) {
right[st.pop()] = i; // 弹出时记录right值
}
left[i] = st.isEmpty() ? -1 : st.peek();
st.push(i);
}
int ret = 0;
for(int i = 0; i < n; i++) {
ret = Math.max(ret, heights[i] * (right[i] - left[i] - 1));
}
return ret;
}
一次遍历法的核心思想是在遍历过程中,每当遇到一个柱子比栈顶柱子矮时,就可以确定栈顶柱子左右边界,并计算以它为高的矩形面积。
java复制public int largestRectangleArea(int[] heights) {
int n = heights.length;
Deque<Integer> st = new ArrayDeque<>();
st.push(-1); // 哨兵节点
int ret = 0;
for(int right = 0; right <= n; right++) {
int h = right < n ? heights[right] : -1; // 最后用-1清空栈
while(st.size() > 1 && heights[st.peek()] >= h) {
int i = st.pop();
int left = st.peek();
ret = Math.max(ret, heights[i] * (right - left - 1));
}
st.push(right);
}
return ret;
}
用数组代替Deque实现栈操作,减少对象创建和函数调用开销,进一步提升性能。
java复制public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] st = new int[n + 1];
int top = -1;
st[++top] = -1; // 栈底哨兵
int ret = 0;
for(int right = 0; right <= n; right++) {
int h = right < n ? heights[right] : -1;
while(top > 0 && heights[st[top]] >= h) {
int i = st[top--];
int left = st[top];
ret = Math.max(ret, heights[i] * (right - left - 1));
}
st[++top] = right;
}
return ret;
}
所有解法的时间复杂度都是O(n),因为每个元素最多入栈和出栈一次。空间复杂度方面:
在实际面试中,这道题常被用作考察候选人对单调栈的理解和应用能力。掌握这个问题的多种解法及其优化思路,对提升算法能力有很大帮助。