柱状图中最大的矩形 - 单调栈

1.题目描述

给定n个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为1
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例:

输入: [2,1,5,6,2,3]
输出: 10

2.题解

public int largestRectangleArea(int[] heights) {
	int n = heights.length;
	int[] left = new int[n];
	int[] right = new int[n];
	
	Stack<Integer> mono_stack = new Stack<Integer>();
	for (int i = 0; i < n; ++i) {
		while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
			mono_stack.pop();
		}
		left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
		mono_stack.push(i);
	}

	mono_stack.clear();
	for (int i = n - 1; i >= 0; --i) {
		while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
			mono_stack.pop();
		}
		right[i] = (mono_stack.isEmpty() ? n : mono_stack.peek());
		mono_stack.push(i);
	}
	
	int ans = 0;
	for (int i = 0; i < n; ++i) {
		ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
	}
	return ans;
}

遍历每根柱子,这根柱子作为所能形成的最大矩形的一部分,计算该矩形的面积。
该矩形两边的柱子高度肯定都小于矩形内的所有柱子的高度。
于是,需要计算每根柱子的左右边界。值得注意的是,这里的第一根柱子的左边界为-1,最后一根柱子的右边界为n(这里是柱子个数)。我们称这里的-1n为哨兵,它们用于处理边界值的问题。
[2,1,5,6,2,3]为例:
遍历第一根柱子,其左边界为-1
遍历第二根柱子,由于其高度小于等于第一根柱子的高度,所以第二根柱子的左边界为-1
遍历第三根柱子,由于其高度大于第二根柱子的高度,所以第三根柱子的左边界为第二根柱子所在的位置1
如果每根柱子的高度单调递增,就将这些柱子(位置)压入栈中。当柱子i的高度小于前一根柱子的高度,前一根柱子出栈,只要栈中的柱子高度大于等于柱子i的高度,就依次出栈,直到栈中的柱子高度小于柱子i的高度或者栈为空。这样就可以找到每根柱子的左边界。
注:这种方法跟接雨水的方法二很相似。

只遍历一次就确定柱子的左右边界的方法:

public int largestRectangleArea(int[] heights) {
	int n = heights.length;
	int[] left = new int[n];
	int[] right = new int[n];
	Arrays.fill(right, n);
	
	Stack<Integer> mono_stack = new Stack<Integer>();
	for (int i = 0; i < n; ++i) {
		while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
			right[mono_stack.peek()] = i;
			mono_stack.pop();
		}
		left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
		mono_stack.push(i);
	}
	
	int ans = 0;
	for (int i = 0; i < n; ++i) {
		ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
	}
	return ans;
}

在确定柱子i的左边界时,栈中的柱子可能出栈,注意到这些出栈的柱子的右边界就是柱子i的位置。
需要注意的是柱子出栈的条件是heights[mono_stack.peek()] >= heights[i],以[2,1,5,5,6,2,3]为例,计算出的right数组为[1,7,3,5,5,7,7],注意到这里计算得到的第三根柱子的右边界是3,而正确的右边界应该是5。但是这不影响计算矩形的最大面积。
因为柱子出栈后,也会把当前遍历的柱子压入栈中,这样计算出的矩形最右边那根柱子的右边界肯定是正确的。
这里用第四根柱子计算出最大矩形的面积,而这个最大矩形的最右边那根柱子是第五跟柱子。
因为遍历第五根柱子时,第四根柱子没有出栈,这样在遍历第六根柱子时,第五根柱子和第四根柱子依次出栈,所以这里第四根柱子和第五根柱子的右边界都是正确的。
注意到第五根柱子的左边界不是最大矩形的左边界,所以这里只能用第四根柱子计算最大矩形的面积。
这里的关键是找到最大矩形中的某根柱子,该柱子的左右边界正好是最大矩形的左右边界。
参考:

posted @ 2021-01-11 15:37  gzhjj  阅读(101)  评论(0编辑  收藏  举报