题目要求
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
思路1:暴力解法
解法一:固定宽度,依次遍历数组,找出最小高度h,即可得出面积值
private static int largestRectangleArea(int[] heights) {
int res = 0;
for(int left = 0; left < heights.length; left++){
int minHeight = Integer.MAX_VALUE;
for(int right = left; right < heights.length; right++){
minHeight = Math.min(minHeight, heights[right]);
res = Math.max(res, minHeight * (right - left + 1));
}
}
return res;
}
该思路时间复杂度为,提交会超出时间限制
解法二:固定高度,依次遍历柱形的高度,对于每一个高度分别向两边扩散,求出以当前高度为矩形的最大宽度多少。
步骤:
-
左边看一下,看最多能向左延伸多长,找到大于等于当前柱形高度的最左边元素的下标;
-
右边看一下,看最多能向右延伸多长;找到大于等于当前柱形高度的最右边元素的下标。
private static int largestRectangleArea(int[] heights) {
int n = heights.length;
if(n == 0){
return 0;
}
int res = 0;
for(int i = 0; i < n; i++){
int left = i, right = i;
int temp = heights[i];
while (left > 0 && heights[left - 1] >= temp){
left--;
}
while (right < n - 1 && heights[right + 1] >= temp){
right++;
}
res = Math.max(res, temp * (right - left + 1));
}
return res;
}
该思路时间复杂度为,提交会超出时间限制
思路二:栈(空间换时间)
暴力解法时间复杂度太高,那么如何能够一次遍历就得到结果?很容易就能想到的思路是空间换时间。
因为固定宽度已经使用了两重for循环,不易优化,所以我们考虑对固定高度的思路进行优化。
首先可以来考虑如何求出一根柱子左侧且最近的小于其高度的柱子。
对于两根柱子j1和j2,如果j1<j2并且,那么对于任意的在它们之后的柱子i(j2<i),j1一定不是i左侧且最近的小于其高度的柱子。
这样我们在枚举到第 i 根柱子的时候,就可以先把所有高度大于等于height[i]的 j值全部移除,剩下的 j值中高度最高的即为答案。在这之后,我们将 i放入数据结构中,开始接下来的枚举。此时,我们需要使用的数据结构就是栈。
具体步骤:
- 栈中存放了 j 值。从栈底到栈顶,j 的值严格单调递增,同时对应的高度值也严格单调递增;
- 当我们枚举到第 i 根柱子时,我们从栈顶不断地移除 height[j]≥height[i] 的 j值。在移除完毕后,栈顶的 j 值就一定满足height[j]<height[i],此时 j 就是 i 左侧且最近的小于其高度的柱子。
这里会有一种特殊情况。如果我们移除了栈中所有的 j 值,那就说明 i 左侧所有柱子的高度都大于height[i],那么我们可以认为 ii 左侧且最近的小于其高度的柱子在位置 j=-1,它是一根「虚拟」的、高度无限低的柱子。这样的定义不会对 我们的答案产生任何的影响,我们也称这根「虚拟」的柱子为「哨兵」。
- 我们再将 i 放入栈顶。
private static int largestRectangleArea(int[] heights){
int res = 0;
Stack<Integer> stack = new Stack<>();
int[] new_heights = new int[heights.length + 2];
for (int i = 1; i < heights.length + 1; i++)
new_heights[i] = heights[i - 1];
System.out.println(Arrays.toString(new_heights));
for (int i = 0; i < new_heights.length; i++) {
System.out.println(stack.toString());
while (!stack.isEmpty() && new_heights[stack.peek()] > new_heights[i]) {
int cur = stack.pop();
res = Math.max(res, (i - stack.peek() - 1) * new_heights[cur]);
}
stack.push(i);
}
return res;
}
优化:利用一个单调递增的栈,找到left和right
private static int largestRectangleArea(int[] heights) {
Stack<Integer> stack = new Stack<>();
int n = heights.length;
int res = 0;
int[] left = new int[n];
int[] right = new int[n];
for(int i = 0; i < n; i++){
while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]){
stack.pop();
}
left[i] = (stack.isEmpty()) ? -1 : stack.peek();
stack.push(i);
}
stack.clear();
for(int i = n - 1; i >= 0; i--){
while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]){
stack.pop();
}
right[i] = stack.isEmpty() ? n : stack.peek();
stack.push(i);
}
for(int i = 0; i < n; i++){
res = Math.max(res, heights[i] * (right[i] - left[i] -1));
}
return res;
}