题目
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
题解
暴力枚举
暴力法虽然时间复杂度往往比较高,但是可以理清思路。
思路
就是枚举以每个柱子高度作为边长的最大矩形面积。
从每个柱子向左和向右检查是否有相同高度或更高的柱子,统计可以形成的矩形的最大宽度,然后计算面积,记录最大的。
复杂度
时间复杂度O(n ^ 2)
:
- 枚举所有柱子需要访问数组所有元素。
- 一次中心扩散最坏情况下要访问数组所有元素,比如所有柱子高度相同。
空间复杂度O(1)
:
- 没有额外空间开销
暴力法多余的时间开销在哪?
中心扩散时,当前柱子的向右扩散,和后面柱子的向左扩散,这两个是重复的动作,应该优化。
应该怎么优化时间开销?
由于要查看每一个柱子高度能够形成多大面积的矩形,所以至少要遍历一次数组。
我们是从左到右遍历数组的,可以想办法避免向右扩散,只向左扩散来计算当前矩形面积,因为左边的柱子已经遍历过了。
那么现在问题变为:
- 遇到什么样的柱子才能向左扩散?
- 向左扩散时,左边的柱子什么时候计算以它们的高度为矩形边长的面积?
遇到什么样的柱子才能向左扩散?
- 看暴力法中向右扩散的条件是,右边的柱子比当前柱子高,才需要扩散。
- 那么右边柱子如果比当前柱子矮,就无法向右扩散,只能向左扩散。
向左扩散左边的柱子什么时候计算以它们的高度为矩形边长的面积?
在遇到不能向右扩散的柱子之前,所遍历过的柱子的特点是:它们高度是从左到右递增或相等的,否则就应该向左扩散了。
假设在遍历时,发现当前柱子比前一个柱子矮,设当前柱子是第i个。
以第i - 1个柱子高度构成的矩形面积怎么求?
- 要寻找左右边界在哪。
- 右边界是知道的,就是i,因为第i - 1个柱子的高度不能向右扩散了。
- 左边界是i - 2,因为第i - 2个柱子到第i - 1个柱子的高度是递增的,第i - 1个柱子的高度不能向左扩散。
以第i - 2个柱子高度构成的矩形面积怎么求?
- 左边界是i - 3,因为左边柱子高度是递增的。
- 右边界在哪,要取决于第i - 2个柱子和第i个柱子哪个更高。
- 如果第i - 2个柱子比第i个柱子高,右边界是i,因为第i - 2个柱子到第i - 1个柱子的高度是递增的,所以第i - 2个柱子可以把矩形扩散到比它高的柱子下面。
- 如果第i - 2个柱子比第i个柱子矮,右边界不是i了,因为第i - 2个柱子的高度还可以向右扩散。
- 什么时候可以计算以第i - 2个柱子高度的矩形面积呢?要在后面遇到比第i - 2个柱子矮的柱子才行,右边界就是后面那个矮柱子的索引,左边界是i - 3。
应该怎么记录左边柱子的信息?
- 由于在遇到高度递减的柱子时,会求以左边柱子高度构成的矩形面积,而且并且有的柱子会求有的柱子不会求,所以要记录下所有没有求过矩形面积的柱子索引,并要能添加和移除,需要用一个集合保存。
- 由于要访问左边柱子的高度,而遍历顺序是从左到右的,所以符合这个数据访问顺序的数据结构是栈。
边界处理
- 计算第一个柱子高度的矩形面积时左边界是-1,取做边界的时候要特殊处理一下。
- 如果末尾的柱子都是递增的,不会触发向左扩散,需要再遍历结束后手动触发一下。
复杂度
时间复杂度O(n)
- 遍历一次数组需要访问n个元素。
- 虽然访问栈需要往回看,但是入栈的元素都是不重复的,并且每个元素只会访问一次,往回看最多访问n个元素。
空间复杂度O(n)
最坏情况下,数组完全递增,栈最多保存n个元素。
1 | import java.util.* |