1. 程式人生 > >POJ 2559 最大矩形面積 poj 3494

POJ 2559 最大矩形面積 poj 3494

給定從左到右多個矩形,已知這此矩形的寬度都為1,長度不完全相等。這些矩形相連排成一排,求在這些矩形包括的範圍內能得到的面積最大的矩形,打印出該面積。所求矩形可以橫跨多個矩形,但不能超出原有矩形所確定的範圍。

建立一個單調遞增棧,所有元素各進棧和出棧一次即可。每個元素出棧的時候更新最大的矩形面積。

設棧內的元素為一個二元組(x, y),x表示矩形的高度,y表示矩形的寬度。

若原始矩形高度分別為2,1,4,5,1,3,3

高度為2的元素進棧,當前棧為(2,1)

高度為1的元素準備進棧,但必須從棧頂開始刪除高度大於或等於1的矩形,因為2已經不可能延續到當前矩形。刪除(2,1)這個元素之後,更新最大矩形面積為2*1=2,然後把它的寬度1累加到當前高度為1的準備進棧的矩形,然後進棧,當前棧為(1,2)

高度為4的元素進棧,當前棧為(1,2) (4,1)

高度為5的元素進棧,當前棧為(1,2) (4,1) (5,1)

高度為1的元素準備進棧,刪除(5,1)這個元素,更新最大矩形面積為5*1=5,把1累加到下一個元素,得到(4,2),刪除(4,2),更新最大矩形面積為4*2=8,把2累加到下一個元素,得到(1,4),1*4=4<8,不必更新,刪除(1,4),把4累加到當前準備進棧的元素然後進棧,當前棧為(1,5)

高度為3的元素進棧,當前棧為(1,5) (3,1)

高度為3的元素準備進棧,刪除(3,1),不必更新,把1累加到當前準備進棧的元素然後進棧,當前棧為(1,5) (3,2)

把餘下的元素逐個出棧,(3,2)出棧,不必更新,把2累加到下一個元素,當前棧為(1,7),(1,7)出棧,不必更新。棧空,結束。

最後的答案就是8。

分析:

如果採用列舉的方式,如果當前我們列舉項是 i = 0, 即 height = 2,

我們用另外兩個變數 j 和k 向左和向右兩個方向搜素,找到第一個 小於 height的下標,這樣我們就找到了用 i 項作為高度長方形了。

我們假設 -1位置,和最右高度都是無窮小。

例如:

i = 0, j = -1, k = 1, 最後的面積是 (k - j - 1) * height = 2

i = 1, j = -1, k = 7, 最後面積是( k - j - 1) * height = 7;

...

i = 3, j = 2, k = 5 面積是 ( k - j - 1) * height = 8

枚舉出所有的長方形的同時,然後得到最後的面積。

不過這樣的程式的時間複雜度是 O(n^2)

我們如何能僅僅做一次,就求出這個面積呢?

觀察:

當我們掃掃描到第一個高度 H1 = 2的時候,我可以標記它的起始位置1, 因為我們還不知道它將向右擴充套件到什麼地方,所以繼續掃面。

當遇到第二項 H2 = 1, 因為這項比之前的小,我們知道,用H1做高度的長方形結束了,算出它的面積。

同時這個時候,我們多了一個高度H2,用它做長方形高度的長方形起始位置應該是在哪裡呢? 因為H1的高度比H2要高,所以這個起始位置自然是H1所在的位置。

為了模擬上面的過程,我們引入單調棧~

我們先定義我們我們要儲存的每一項資料

struct Node

{

      int height;

      int startPosition;

};

用來描述某一個高度,和這個高度的起始位置。

然後我們按照高度來組織成單調棧。我們來看一下它是如何工作的。

為了不用考慮堆疊為空的情況,我們用插入棧底 一個高度(0, 0)的項。

資料:

 2 1 4 5 1 3 3

這樣初始化

(0 , 0)

I = 1

當掃描到(2, 1)時候,因為高度2 大於棧頂,插入

(0, 0),  (2, 1)

I = 2:

當掃描到1的時候,因為1小於棧頂高度2, 我們認為棧頂的那個高度應不能再向右擴充套件了,所以我們將它彈出

這個時候掃描到 i = 2;

高度是 (i - 1(H1.startIndex)) * H1.height = 2;

我們得到一個面積是2的長方形。

同時我們發現高度是1的當前高度,可以擴充套件到 H1所在的下標,所以我們插入( 1, 1) 堆疊變成

(0, 0), (1, 1) 因為(2, 1)已經不能向右伸展了,已經被彈出了

i = 3

(0, 0), (1, 1), ( 4 3)

i = 4

(0, 0), (1, 1), (4, 3), (5, 4)

i = 5

這個時候當前高度小於棧頂高度,我們認為棧頂已經不能向右擴充套件,所以彈出,並且獲得面積 ( i  - H5.startindex) * H5.height = (5 - 4 ) * 5 = 5

彈出這個元素後,其實(4, 3)的高度也要比 1 大,所以把這個也彈出來,同樣方式獲得面積 8.

最後我們的堆疊是

(0, 0) , (1, 1)

i  = 6

(0, 0), (1, 1), ( 3, 6)

i = 7

(0, 0), (1, 1), (3, 6)

i = 8

最後一步是有點特殊的,因為我們必須要把所有的元素都彈出來,因為棧裡面的高度,都堅持到了最後,我們要把這些高度組成的長方形拿出來檢測。

我們可以假設掃面到8的時候,高度是0,(最小值)

彈出(3,6)獲得面積 (8 - 6 ) * 3 = 6

彈出(1, 1)獲得面積(8 - 1) * 1 = 7

最後的面積是8.

#include <iostream>
#include <cstdio>
using namespace std;

const int N = 100005;

struct Elem
{
	int height;
	int count;
};

Elem stack[N];
int top;

int main()
{
	int height, n;
	long long ans, tot, tmp;
	while (scanf("%d", &n) != EOF && n)
	{
		top = 0;
		ans = 0;
		for (int i = 0; i < n; ++i)
		{
			scanf("%d", &height);
			tmp = 0;
			while (top > 0 && stack[top - 1].height >= height)
			{
				tot = stack[top - 1].height * (stack[top - 1].count + tmp);
				if (tot > ans) ans = tot;
				tmp += stack[top - 1].count;
				--top;
			}
			stack[top].height = height;
			stack[top].count = 1 + tmp;
			++top;
		}
		tmp = 0;
		while (top > 0)
		{
			tot = stack[top - 1].height * (stack[top - 1].count + tmp);
			if (tot > ans) ans = tot;
			tmp += stack[top - 1].count;
			--top;
		}
		printf("%lld\n", ans);
	}
	return 0;
}

題目簡述

題目的描述很簡單,在一個M * N的矩陣中,所有的元素只有0和1, 找出只包含1的最大矩形。

例如:圖中是一個4 × 6的矩形,畫出紅色的是我們要找到的區域。



最開始見過這個題目是在看一個人寫的面經裡面,當時完全沒有感覺,不知道怎麼做。後來知道了一個東西叫單調棧然後做了一些題,居然發現POJ上的這個題目,和那個面試題一模一樣。

所以就研究明白,分享一下。

題目分析:

這題目如果用暴力做的話,方法是很顯然的,對圖中的任意點,(i, j), 向它的右下側找到一個右下端點( r, u), 然後檢測方塊 (i, j) -> (r, u) 是不是全1. 如果是全1, 我們就找到一個合適的矩形1. 在列舉的同時更新找到最大矩形。

如果這樣的話,列舉複雜度是O(M * N), 找下端點 複雜度是 O (M * N), 然後檢測的複雜度也是 O(M*N) 最後演算法的複雜度是 O(M^3 * N^3)

顯然這個複雜度過了點。

仔細觀察發現(這個思路也是別人提醒的,感覺自己現在依然不能很準確的建模):

因為我們要找的是矩形,所以它一定是以 某個行元素開始的,如果列舉行的時候,我們會發現:

對於第一行:



對於第二行:



第三行:



第四行:



這樣的話,其實我們要找到的某個矩形就轉換成 一某一個行開始的 histogram的最大矩形問題了。

那麼我們原始矩形可以變成如下的形式的資料:


第一行表示,我們以第一行作為底邊,所形成的 histogram的高度,其他行也類似。

所以問題變成了 列舉每一行,然後求出每一行對應的histogram的最大矩形。

關於histogram求最大矩形

因為到了這裡以後,基本的原理和histogram是一樣的,所以就不再重複了。