LeetCode_直方图最大面积(Largest Rectangle in Histogram)
来源:互联网 发布:上海知柚网络公司市值 编辑:程序博客网 时间:2024/06/03 13:05
一、题目
Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.
Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3].
The largest rectangle is shown in the shaded area, which has area = 10 unit.
For example,Given height = [2,1,5,6,2,3],return 10.
LeetCode 84题:Largest Rectangle in Histogram,给定一个直方图(下图a),求直方图中能够组成的所有矩形中,面积最大为多少。对于图a来说,我们很容易看出来面积最大的矩形为高度为5和6的直方图组成的矩形(图b隐形部分),其面积为5 * 2 = 10。
二、解题
1.感想
如果就对于图片的示例而言,许多人一看就知道答案了,压根就不需要太多的思考,可是正是因为这种定性的思考,让我们慢慢没法“知其所以然”,也让我们慢慢失去了举一反三的思考方式,就图片的示例而言,之所以我们一看就能看出答案,是因为示例的例子数据太少了,如果数据很多的时候我们能一看就看出答案吗?所以无论面对什么样的题目,我们都不能放弃“知其所以然”,从根本解决问题。
2.暴力搜索
这道题目一个显而易见的解决方法就是暴力搜索:找出所有可能的矩形,然后求出面积最大的那个。要找出所有可能的矩形,只需要从左到右扫描每个立柱,然后以这个立柱为矩形的左边界(假设为第i个),再向右扫面,分别以(i+1, i+2, n)为右边界确定矩形的形状。
这符合我们本能的思考过程:要找出最大的一个,就先列出所有的可能,比较大小后求出最大的那个。然而不幸的是,本能的思考过程通常是简单粗暴而又低效的,就这个题目来说,时间复杂度为N^2 。
3.使用动态规划
用left[i]表示第i个柱子可以最多向左延伸至第left[i]个柱子,形成一个矩形,right[i]则表示向右延伸。遍历两次,分别计算出这两个数组。
再遍历一次,即可求出所有的柱子可以形成的最大的矩形面积。为了减少边界的判断,可以使用哨兵,在两端添加两个柱子高度都为-1.
public class LargestRectangle { public static void main(String[] args) { int height[] = { 2, 1, 5, 6, 2, 3 }; int ans = getMaxRectangle(height); // lift[]=[0,1,1,3,4,3,6] right[]=[0,1,6,4,4,6,6] System.out.println("最大的面积位:" + ans); } /** * 遍历每个柱子可以组成的最大面积 * * @param heights * @return 最大的面积 */ public static int getMaxRectangle(int heights[]) { int ans = 0; int n = heights.length; int left[] = new int[n + 1]; int right[] = new int[n + 1]; processLR(heights, left, right); for (int i = 1; i <= n; i++) { int tmp = (right[i] - left[i] + 1) * heights[i - 1]; if (ans < tmp) ans = tmp; } return ans; } /** * 初始化左右两个数组 * * @param heights * @param left * @param right */ public static void processLR(int heights[], int left[], int right[]) { int n = heights.length; // 用临时数组,设置两个哨兵 int tempArr[] = new int[n + 2]; tempArr[0] = -1; for (int i = 1; i <= n; i++) tempArr[i] = heights[i - 1]; tempArr[tempArr.length - 1] = -1; // 用left[i]表示第i个柱子可以最多向左延伸至第left[i]个柱子 for (int i = 1; i <= n; i++) { int k = i; while (tempArr[i] <= tempArr[k - 1]) k = left[k - 1]; left[i] = k; } // 用right[i]表示第i个柱子可以最多向右延伸至第right[i]个柱子 for (int i = n; i > 0; i--) { int k = i; while (tempArr[i] <= tempArr[k + 1]) k = right[k + 1]; right[i] = k; } }}
运行的结果:
4.巧妙使用栈
在网上发现另外一个使用一个栈的O(n)解法,代码非常简洁,栈内存储的是高度递增的下标。对于每一个直方图高度,分两种情况。1:当栈空或者当前高度大于栈顶下标所指示的高度时,当前下标入栈。否则,2:当前栈顶出栈,并且用这个下标所指示的高度计算面积。而这个方法为什么只需要一个栈呢?因为当第二种情况时,for循环的循环下标回退,也就让下一次for循环比较当前高度与新的栈顶下标所指示的高度,注意此时的栈顶已经改变由于之前的出栈。
public class largestRectangleArea { public static void main(String[] args) { int height[] = { 2, 1, 5, 6, 2, 3 }; int ans = largestRectangleArea(height); System.out.println("最大的面积位:" + ans); } // O(n) using one stack public static int largestRectangleArea(int[] height) { // Start typing your Java solution below // DO NOT write main() function int area = 0; java.util.Stack<Integer> stack = new java.util.Stack<Integer>(); for (int i = 0; i < height.length; i++) { if (stack.empty() || height[stack.peek()] < height[i]) { stack.push(i); } else { int start = stack.pop(); int width = stack.empty() ? i : i - stack.peek() - 1; area = Math.max(area, height[start] * width); i--; } } while (!stack.empty()) { int start = stack.pop(); int width = stack.empty() ? height.length : height.length - stack.peek() - 1; area = Math.max(area, height[start] * width); } return area; }}
这是看到别人的推演过程,之前看到,就添加到笔记了,没有原著的地址,所以没法标明出处:
一个思维历程
那么这个算法真的就是我等凡夫俗子不能想出来的?难道我们只能仰望高山,恨自己智商不高?我还真不服气呢,于是又静下心去思考这个问题。
这次我们不从已知条件推结果,而直接从结论入手,就是说假设现在已经找到了面积最大的那个矩形。接着我们来分析该矩形有什么特征,然后可以用下面两种方法之一来缩减问题的规模(因为这两种方法都不用找出所有的矩形一一比较)。
找出满足这些特征的矩形,面积最大的矩形肯定是其中之一;
排除那些不满足这些特征的矩形,面积最大的矩形在剩下的那些矩形里面。
为了使考虑情况尽可能全面,画了许多直方图,防止使用原题目图片可能存在的一些特定假设,其中一个直方图如下图:题目情况分析
通过不断地对多个直方图的观察,发现面积最大的那个矩形好像都包含至少一个完整的bar,那么这条规律适用于所有的直方图吗?我们用反证法来证明,假设某个最大矩形中每个竖直块都是所在的bar的一小段,那么这个矩形高度增加1后仍然是一个合法的矩形,但新的矩形面积更大,与假设矛盾,所以面积最大的矩形必须至少有一个竖直块是整个bar。
至此我们找到了面积最大矩形的一个特性:各组成竖直块中至少有一个是完整的Bar。有了这条特性,我们再找面积最大的矩形时,就有了一个比较小的范围。具体来说就是针对每个bar,我们找出包含这个bar的面积最大的矩形,然后只需要比较这N个矩形即可(N为bar的个数)。
那么问题又来了,如何找出“包含某个bar的面积最大的矩形呢”?对于上面的直方图,包含下标为4的bar的最大矩形如下图橘黄色部分:
局部最大矩形
简单观察一下,就会发现要找到包含某个bar的最大矩形其实很简答,只需要找到高度小于该bar的左、右边界即可,上图中分别是下标为1的bar和下标为10的bar。
至此问题已经变为“对于给定的bar,如何确定高度比它小的左、右边界”。其实求左边界和右边界是同样的求法,下面我们考虑求每个bar的左边界。最直接的思路是对于每个bar,扫面其前面所有的bar,找出最后一个高度小于它的bar,这样的话时间复杂度明显又是N^2 ,Holy Shit。
到这里似乎没有路可走了,但如果我们继续绞尽脑汁地去想,可能(或许你对栈理解的很深入,或许是你在一个类似的问题中用到了栈,当然你也可能想到动态规划的思想,那也是可行的)会联想到栈这一数据结构。用栈维护一个高度递增的bar的集合,也就是说栈底到栈顶部对应的bar的高度越来越大。那么对应一个刚读入的bar,我们只需要比较它的高度和栈顶对应bar的高度,如果当前bar比较高,则弹出栈顶元素继续比较,直到栈顶bar比它低或者栈为空。之后,将当前bar入栈,更新栈内的递增序列。
我们从左到右扫一遍得到每个bar对应的左边界,然后从右到左扫一遍得到bar的右边界。两次扫描过程中,每个bar都只有出栈、入栈操作,所以时间复杂度为O(N)。通过这样的预处理,即可以O(N)的时间复杂度得到每个bar的左右边界。之后对于每个bar求出包含它的最大面积,也即是由左右边界和bar的高度围起来的矩形的面积。再做N次比较,即可得出最终的结果。
这里先预处理用两个栈扫描两次得到左、右边界,再计算面积,是按照推导过程一步一步来的。当我们写完程序后,再综合看这个问题,可能会发现其实没必要这样分开来做,我们可以在扫描的同时,维护一个递增的栈,同时在“合适的”时候计算面积,然后更新最大面积。具体实现方法就是前面给出的那个神奇的算法,不过现在看来一点也不神奇了,我们已经探索到了它背后的思维历程。
当然,条条道路通罗马,上面思维过程只是其中一条通往解决方案的路径,你可能以另一种思维过程找到了答案。不过,我们上面的整个推导过程没有涉及一些类似“神谕”的启发,只是一些简单的方法:比如从结论推导、反证法、归纳总结、联想(可能联想到栈有点难)等,因此每个人都可以学会,并且很容易被大脑记住。值得注意的是,我们的整个思考过程并不简简单单地跟上面写的那样是线性的,它更可能是树形的,只是我们剪去了那些后来证明行不通的枝。
Demo:http://download.csdn.net/detail/two_water/9670562
三、思考
最后奉上看到别人写的东西,略有感想,故分享。
- LeetCode_直方图最大面积(Largest Rectangle in Histogram)
- LeetCode: Largest Rectangle in Histogram(直方图最大面积)
- Largest Rectangle in Histogram (直方图中最大面积) 【leetcode】
- Largest Rectangle in Histogram 直方图中最大的矩形面积
- 【直方图的最大面积】Largest Rectangle in Histogram
- 4.1.3LeetCode: Largest Rectangle in Histogram(直方图最大面积)
- ACM-直方图最大面积-Largest Rectangle in Histogram
- [LeetCode]—Largest Rectangle in Histogram 求直方图最大填充矩形面积
- LeetCode | Largest Rectangle in Histogram(直方图围城的最大矩形面积)
- 直方图最大矩形问题(Largest Rectangle in Histogram)
- 直方图最大矩形问题(Largest Rectangle in Histogram)
- 寻找直方图中的最大矩形 Largest Rectangle in Histogram
- Largest Rectangle in Histogram 直方图的最大正方形
- Largest Rectangle in Histogram 直方图中最大的矩形
- LeetCode 84. Largest Rectangle in Histogram(直方图最大矩形)
- [Leetcode] largest rectangle in histogram 直方图中最大的矩形
- leetcode 84. Largest Rectangle in Histogram 最大直方图
- Largest Rectangle in Histogram :直方图中的最大三角形
- 分库分表
- Android studio如何使用SVN进行版本控制?
- 【Android】给Android Studio设置代理
- Android Dalvik虚拟机
- maya extrude by python
- LeetCode_直方图最大面积(Largest Rectangle in Histogram)
- Remove Duplicates from Sorted List II
- 为每一个CPU执行一次DPC例程
- 念数字
- EasyChair提交会议论文的方法简介-转载
- 这是我的第一篇博客
- struts2 环境搭建
- mac的一点点使用心得
- 代码分析工具推荐Understand