我有一个整数高度和恒定宽度1的直方图。我想在直方图下最大化矩形区域。 e.g:
_
| |
| |_
| |
| |_
| |
对此的答案是6,3 * 2,使用col1和col2。
O(n ^ 2)蛮力对我来说很清楚,我想要一个O(n log n)算法。我试图按照最大增加子序列O(n log n)算法的方式来思考动态编程,但我没有继续前进。我应该使用分而治之的算法吗?
PS:如果没有这样的解决方案,请求具有足够声誉的人删除分而治之标签。
在mho的评论之后:我的意思是最大的矩形区域完全适合。 (感谢j_random_hacker澄清:))。
答案 0 :(得分:67)
上述答案在代码中给出了最好的O(n)解决方案,然而,他们的解释很难理解。使用堆栈的O(n)算法起初对我来说似乎很神奇,但是现在它对我来说都很有意义。好的,让我解释一下。
首先观察:
要查找最大矩形,如果对于每个条形x
,我们知道其每一侧的第一个较小的条形,假设为l
和r
,我们确定height[x] * (r - l - 1)
1}}是使用条形x
的高度我们可以获得的最佳镜头。在下图中,1和2是5中的第一个。
好的,我们假设我们可以在O(1)时间内为每个条形图执行此操作,然后我们可以在O(n)中解决此问题!扫描每个酒吧。
然后,问题出现了:对于每一个酒吧,我们能否在O(1)时间内在左侧和右侧找到第一个较小的小条?那似乎不可能吧? ......通过使用不断增加的堆栈,这是可能的。
为什么使用增加的堆栈可以跟踪左右第一个较小的?
也许通过告诉你一个不断增加的筹码可以完成这项工作并不令人信服,所以我会引导你完成这个。
首先,为了保持堆栈增加,我们需要一个操作:
while x < stack.top():
stack.pop()
stack.push(x)
然后你可以检查在增加的堆栈中(如下所示),对于stack[x]
,stack[x-1]
是左边第一个较小的,然后是一个可以弹出stack[x]
的新元素out是右边第一个较小的。
还是不能相信堆栈[x-1]是堆栈[x]左边第一个小的?
我将通过矛盾来证明这一点。
首先,stack[x-1] < stack[x]
是肯定的。但我们假设stack[x-1]
不是stack[x]
左侧的第一个较小的。
那么第一个较小的fs
在哪里?
If fs < stack[x-1]:
stack[x-1] will be popped out by fs,
else fs >= stack[x-1]:
fs shall be pushed into stack,
Either case will result fs lie between stack[x-1] and stack[x], which is contradicting to the fact that there is no item between stack[x-1] and stack[x].
因此,堆栈[x-1]必须是第一个较小的。
<强>要点:强>
增加堆栈可以跟踪每个元素左右第一个较小的元素。通过使用此属性,可以通过使用O(n)中的堆栈来解决直方图中的最大矩形。
恭喜!这真的是一个棘手的问题,我很高兴我的平淡无奇的解释并没有阻止你完成。附件是我证明的解决方案作为你的奖励:)
def largestRectangleArea(A):
ans = 0
A = [-1] + A
A.append(-1)
n = len(A)
stack = [0] # store index
for i in range(n):
while A[i] < A[stack[-1]]:
h = A[stack.pop()]
area = h*(i-stack[-1]-1)
ans = max(ans, area)
stack.append(i)
return ans
答案 1 :(得分:43)
除蛮力方法外,还有三种方法可以解决这个问题。我会写下所有这些。 java代码在一个名为leetcode的在线评判网站http://www.leetcode.com/onlinejudge#question_84中通过了测试。所以我相信代码是正确的。
解决方案1:动态编程+ n * n矩阵作为缓存
时间:O(n ^ 2),空间:O(n ^ 2)
基本思想:使用n * n矩阵dp [i] [j]缓存bar [i]和bar [j]之间的最小高度。从宽度为1的矩形开始填充矩阵。
public int solution1(int[] height) {
int n = height.length;
if(n == 0) return 0;
int[][] dp = new int[n][n];
int max = Integer.MIN_VALUE;
for(int width = 1; width <= n; width++){
for(int l = 0; l+width-1 < n; l++){
int r = l + width - 1;
if(width == 1){
dp[l][l] = height[l];
max = Math.max(max, dp[l][l]);
} else {
dp[l][r] = Math.min(dp[l][r-1], height[r]);
max = Math.max(max, dp[l][r] * width);
}
}
}
return max;
}
解决方案2:动态编程+ 2个数组作为缓存。
时间:O(n ^ 2),空格:O(n)
基本思路:此解决方案与解决方案1类似,但节省了一些空间。我们的想法是,在解决方案1中,我们构建了从第1行到第n行的矩阵。但是在每次迭代中,只有前一行有助于构建当前行。所以我们依次使用两个数组作为前一行和当前行。
public int Solution2(int[] height) {
int n = height.length;
if(n == 0) return 0;
int max = Integer.MIN_VALUE;
// dp[0] and dp[1] take turns to be the "previous" line.
int[][] dp = new int[2][n];
for(int width = 1; width <= n; width++){
for(int l = 0; l+width-1 < n; l++){
if(width == 1){
dp[width%2][l] = height[l];
} else {
dp[width%2][l] = Math.min(dp[1-width%2][l], height[l+width-1]);
}
max = Math.max(max, dp[width%2][l] * width);
}
}
return max;
}
解决方案3:使用堆栈。
时间:O(n),空格:O(n)
此解决方案很棘手,我从explanation without graphs和explanation with graphs学习了如何执行此操作。我建议你在阅读下面的解释之前阅读这两个链接。没有图表很难解释,所以我的解释可能难以理解。
以下是我的解释:
对于每个栏,我们必须能够找到包含此栏的最大矩形。所以这些n个矩形中最大的一个是我们想要的。
要获得某个条形的最大矩形(比如bar [i],第(i + 1)条),我们只需找出最大的区间 包含这个栏。我们所知道的是,这个区间中的所有条都必须至少与bar [i]相同。所以,如果我们弄清楚有多少 在bar [i]的左边有连续的相同高度或更高的条形,并且在条形图的右边有多少连续的相同高度或更高的条形[i],我们 将知道间隔的长度,即bar [i]的最大矩形的宽度。
要计算bar [i]左边的连续相同高度或更高的条形数量,我们只需要找到左边最近的较短的条形 因为bar [i]之间的所有条形将是连续的相同高度或更高的条形。
我们使用堆栈动态跟踪所有比某个条形更短的左边条。换句话说,如果我们从第一个栏重复到bar [i],当我们到达栏[i]并且没有更新堆栈时, 堆栈应存储不高于bar [i-1]的所有条形,包括bar [i-1]本身。我们将bar [i]的高度与堆栈中的每个条形进行比较,直到我们找到一个比bar [i]更短的条,这是最短的条形。 如果bar [i]高于堆栈中的所有柱,则表示bar [i]左侧的所有柱都高于bar [i]。
我们可以在第i个栏的右侧做同样的事情。然后我们知道bar [i]区间有多少条。
public int solution3(int[] height) {
int n = height.length;
if(n == 0) return 0;
Stack<Integer> left = new Stack<Integer>();
Stack<Integer> right = new Stack<Integer>();
int[] width = new int[n];// widths of intervals.
Arrays.fill(width, 1);// all intervals should at least be 1 unit wide.
for(int i = 0; i < n; i++){
// count # of consecutive higher bars on the left of the (i+1)th bar
while(!left.isEmpty() && height[i] <= height[left.peek()]){
// while there are bars stored in the stack, we check the bar on the top of the stack.
left.pop();
}
if(left.isEmpty()){
// all elements on the left are larger than height[i].
width[i] += i;
} else {
// bar[left.peek()] is the closest shorter bar.
width[i] += i - left.peek() - 1;
}
left.push(i);
}
for (int i = n-1; i >=0; i--) {
while(!right.isEmpty() && height[i] <= height[right.peek()]){
right.pop();
}
if(right.isEmpty()){
// all elements to the right are larger than height[i]
width[i] += n - 1 - i;
} else {
width[i] += right.peek() - i - 1;
}
right.push(i);
}
int max = Integer.MIN_VALUE;
for(int i = 0; i < n; i++){
// find the maximum value of all rectangle areas.
max = Math.max(max, width[i] * height[i]);
}
return max;
}
答案 2 :(得分:15)
在the @IVlad's answer O(n)解决方案的Python中实现:
from collections import namedtuple
Info = namedtuple('Info', 'start height')
def max_rectangle_area(histogram):
"""Find the area of the largest rectangle that fits entirely under
the histogram.
"""
stack = []
top = lambda: stack[-1]
max_area = 0
pos = 0 # current position in the histogram
for pos, height in enumerate(histogram):
start = pos # position where rectangle starts
while True:
if not stack or height > top().height:
stack.append(Info(start, height)) # push
elif stack and height < top().height:
max_area = max(max_area, top().height*(pos-top().start))
start, _ = stack.pop()
continue
break # height == top().height goes here
pos += 1
for start, height in stack:
max_area = max(max_area, height*(pos-start))
return max_area
示例:
>>> f = max_rectangle_area
>>> f([5,3,1])
6
>>> f([1,3,5])
6
>>> f([3,1,5])
5
>>> f([4,8,3,2,0])
9
>>> f([4,8,3,1,1,0])
9
复制粘贴算法的描述(如果页面出现故障):
我们处理元素 从左到右的顺序并保持一个 关于开始的一堆信息但是 尚未完成的亚组织图。每当 一个新元素到来它受到了 遵守以下规则。如果堆栈 是空的我们打开一个新的子问题 将元素推入堆栈。 否则我们将它与元素进行比较 在堆栈顶部。如果新的是 更大的我们再次推动它。如果是新的 一个是平等的我们跳过它。在所有这些 案件,我们继续下一个新的 元件。如果新的更少,我们 完成最顶层的子问题 更新最大面积w.r.t.该 堆栈顶部的元素。然后, 我们丢弃顶部的元素,并且 重复保持的程序 目前的新元素。这样,全部 子问题完成直到 堆栈变空,或者它的顶部 element小于或等于 新元素,导致行动 如上所述。如果所有元素都有 已经处理,堆栈不是 但是空了,我们完成剩下的 通过更新最大值来解决子问题 地区w.r.t.对于元素 顶部。
更新w.r.t.一个元素,我们 找到最大的矩形 包括那个元素。观察一下 更新最大区域 除了那些之外的所有元素 跳过。如果跳过某个元素, 然而,它具有相同的最大值 矩形作为元素的顶部 堆叠在那个时候将是 稍后更新。的高度 当然,最大的矩形是 元素的价值。在那个时间 更新,我们知道有多远 最大的矩形向右延伸 元素,因为那时,为了 第一次,一个较小的新元素 身高来了。信息,如何 最大的矩形延伸到 元素的左边是可用的 如果我们也将它存储在堆栈中。
因此我们修改了程序 如上所述。如果是新元素 立刻推,要么是因为 堆栈为空或大于 堆栈的顶部元素, 包含它的最大矩形 向左延伸不远 当前的元素。如果被推了 经过几个元素之后 弹出堆栈,因为它是 少于这些元素,最大的 包含它的矩形延伸到 离开最远的那个 最近弹出的元素。
每个元素都被推送并弹出 大多数曾经和每一步 程序至少有一个要素是 推或弹。自金额 为决策和更新工作 是不变的,复杂的 算法是通过摊销的O(n) 分析
答案 3 :(得分:5)
O(N)
中最简单的解决方案long long getMaxArea(long long hist[], long long n)
{
stack<long long> s;
long long max_area = 0;
long long tp;
long long area_with_top;
long long i = 0;
while (i < n)
{
if (s.empty() || hist[s.top()] <= hist[i])
s.push(i++);
else
{
tp = s.top(); // store the top index
s.pop(); // pop the top
area_with_top = hist[tp] * (s.empty() ? i : i - s.top() - 1);
if (max_area < area_with_top)
{
max_area = area_with_top;
}
}
}
while (!s.empty())
{
tp = s.top();
s.pop();
area_with_top = hist[tp] * (s.empty() ? i : i - s.top() - 1);
if (max_area < area_with_top)
max_area = area_with_top;
}
return max_area;
}
答案 4 :(得分:2)
使用Divide and Conquer还有另一种解决方案。它的算法是:
1)将阵列分成2个部分,其中最小高度为断点
2)最大面积是: a)阵列的最小高度*大小 b)左半数组中的最大矩形 c)右半数组中的最大矩形
时间复杂性来自O(nlogn)
答案 5 :(得分:2)
堆栈解决方案是迄今为止我见过的最聪明的解决方案之一。并且可能有点难以理解为什么这样有效。
我已经详细解释了同样的问题here。
帖子摘要点: -
constraint/min
值,左右极端的最佳可能性是什么? 因此,如果我们遍历数组中每个可能的min
。每个值的左右极值是什么?
current min
,同样也是第一个最右边的值小于当前最小值。所以现在我们需要看看我们是否能找到一种聪明的方法来找到小于当前值的第一个左右值。
想想:如果我们遍历数组部分说到min_i,那么如何建立min_i + 1的解决方案?
我们需要第一个小于min_i的值。
所有这些都会导致我们使用我们自己的stack
结构。
因此,对于每个最小值,要找到其左侧较小的值,我们执行以下操作: -
很难解释这一点,但如果这是有道理的,那么我建议阅读完整的文章here,因为它有更多的见解和细节。
答案 6 :(得分:1)
我不理解其他条目,但我想我知道如何在O(n)中进行如下操作。
A)为每个索引找到直方图内最大的矩形,该矩形在索引列接触矩形的顶部并记住矩形开始的位置。这可以使用基于堆栈的算法在O(n)中完成。
B)类似地,对于每个索引,找到从该索引处开始的最大矩形,其中索引列接触矩形的顶部并记住矩形的结束位置。 O(n)也使用与(A)相同的方法,但向后扫描直方图。
C)对于每个索引,组合(A)和(B)的结果以确定该索引处的列接触矩形顶部的最大矩形。 O(n)如(A)。
D)由于最大矩形必须被直方图的某一列触及,因此最大的矩形是步骤(C)中找到的最大矩形。
困难的部分是实施(A)和(B),我认为这是JF Sebastian可能解决的问题,而不是所述的一般问题。
答案 7 :(得分:1)
我对这个进行了编码,从某种意义上来说感觉好一点:
import java.util.Stack;
class StackItem{
public int sup;
public int height;
public int sub;
public StackItem(int a, int b, int c){
sup = a;
height = b;
sub =c;
}
public int getArea(){
return (sup - sub)* height;
}
@Override
public String toString(){
return " from:"+sup+
" to:"+sub+
" height:"+height+
" Area ="+getArea();
}
}
public class MaxRectangleInHistogram {
Stack<StackItem> S;
StackItem curr;
StackItem maxRectangle;
public StackItem getMaxRectangleInHistogram(int A[], int n){
int i = 0;
S = new Stack();
S.push(new StackItem(0,0,-1));
maxRectangle = new StackItem(0,0,-1);
while(i<n){
curr = new StackItem(i,A[i],i);
if(curr.height > S.peek().height){
S.push(curr);
}else if(curr.height == S.peek().height){
S.peek().sup = i+1;
}else if(curr.height < S.peek().height){
while((S.size()>1) && (curr.height<=S.peek().height)){
curr.sub = S.peek().sub;
S.peek().sup = i;
decideMaxRectangle(S.peek());
S.pop();
}
S.push(curr);
}
i++;
}
while(S.size()>1){
S.peek().sup = i;
decideMaxRectangle(S.peek());
S.pop();
}
return maxRectangle;
}
private void decideMaxRectangle(StackItem s){
if(s.getArea() > maxRectangle.getArea() )
maxRectangle = s;
}
}
请注意:
Time Complexity: T(n) < O(2n) ~ O(n)
Space Complexity S(n) < O(n)
答案 8 :(得分:1)
我要感谢@templatetypedef他/她非常详细和直观的答案。下面的Java代码基于他建议使用笛卡尔树并解决O(N)时间和O(N)空间中的问题。我建议您在阅读下面的代码之前阅读@ templatetypedef的答案。代码以leetcode:https://leetcode.com/problems/largest-rectangle-in-histogram/description/的问题解决方案的格式给出,并传递所有96个测试用例。
class Solution {
private class Node {
int val;
Node left;
Node right;
int index;
}
public Node getCartesianTreeFromArray(int [] nums) {
Node root = null;
Stack<Node> s = new Stack<>();
for(int i = 0; i < nums.length; i++) {
int curr = nums[i];
Node lastJumpedOver = null;
while(!s.empty() && s.peek().val >= curr) {
lastJumpedOver = s.pop();
}
Node currNode = this.new Node();
currNode.val = curr;
currNode.index = i;
if(s.isEmpty()) {
root = currNode;
}
else {
s.peek().right = currNode;
}
currNode.left = lastJumpedOver;
s.push(currNode);
}
return root;
}
public int largestRectangleUnder(int low, int high, Node root, int [] nums) {
/* Base case: If the range is empty, the biggest rectangle we
* can fit is the empty rectangle.
*/
if(root == null) return 0;
if (low == high) {
if(0 <= low && low <= nums.length - 1) {
return nums[low];
}
return 0;
}
/* Assume the Cartesian tree nodes are annotated with their
* positions in the original array.
*/
int leftArea = -1 , rightArea= -1;
if(root.left != null) {
leftArea = largestRectangleUnder(low, root.index - 1 , root.left, nums);
}
if(root.right != null) {
rightArea = largestRectangleUnder(root.index + 1, high,root.right, nums);
}
return Math.max((high - low + 1) * root.val,
Math.max(leftArea, rightArea));
}
public int largestRectangleArea(int[] heights) {
if(heights == null || heights.length == 0 ) {
return 0;
}
if(heights.length == 1) {
return heights[0];
}
Node root = getCartesianTreeFromArray(heights);
return largestRectangleUnder(0, heights.length - 1, root, heights);
}
}
答案 9 :(得分:-1)
您可以使用O(n)方法使用堆栈计算直方图下的最大面积。
long long histogramArea(vector<int> &histo){
stack<int> s;
long long maxArea=0;
long long area= 0;
int i =0;
for (i = 0; i < histo.size();) {
if(s.empty() || histo[s.top()] <= histo[i]){
s.push(i++);
}
else{
int top = s.top(); s.pop();
area= histo[top]* (s.empty()?i:i-s.top()-1);
if(area >maxArea)
maxArea= area;
}
}
while(!s.empty()){
int top = s.top();s.pop();
area= histo[top]* (s.empty()?i:i-s.top()-1);
if(area >maxArea)
maxArea= area;
}
return maxArea;
}
有关说明,请参阅此处http://www.geeksforgeeks.org/largest-rectangle-under-histogram/