POJ.2559[leetcode.84]直方圖最大矩形及二維情況
阿新 • • 發佈:2019-01-08
暴力 O(n^2)
最簡單粗暴的辦法是對於每個方塊,我們求出它向左和向右能延伸的最遠距離,再乘以它的自身高度就行了。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
#define clr( a , x ) memset ( a , x , sizeof (a) );
#define RE freopen("1.in","r",stdin);
const int maxn = 1005;
int largestRectangleArea(int* heights, int heightsSize) {
int ans = 0;
for (int i = 0; i < heightsSize; i++) {
int dis = 0; //i方塊能延伸的最遠距離
for (int j = i + 1; j < heightsSize; j++) {
if ( heights[i] <= heights[j]) dis++;
else break;
}
for (int j = i; j >= 0; j--) {
if ( heights[i] <= heights[j]) dis++;
else break;
}
dis = heights[i] * dis;
if (dis > ans) ans = dis;
}
return ans;
}
int main()
{
// RE
int heights[maxn];
int n;
while ( scanf("%d", &n)!=EOF ) {
for (int i = 0; i < n; i++)
scanf("%d", &heights[i]);
printf("%d\n", largestRectangleArea(heights, n));
}
return 0;
}
O(n) 解法
對於每個柱子,以它為高度,它往左往右都最多隻能延伸到第一個比它矮的柱子。
所以可以維護一個值遞增的棧(每遍歷完一個就加一個,棧裡值為柱子對應下標,但這些柱高是遞增的 ),掃描一次柱子,取棧頂,如果 棧頂柱子 比當前柱子高,那以棧頂柱子的高度為根基,最右能延伸到當前柱子,最左能延伸到 棧頂柱子的下一個棧頂(即pop2次,因為棧是遞增的),就可以 以棧頂柱子的高度,當前柱子 - 棧頂的下一個棧頂 - 1 為寬度。 然後把當前柱子加到棧裡。
怎麼保證棧遞增?
因為每次把當前元素入棧前,都會彈出棧裡比當前高的,所以棧裡的元素高度一定是遞增的。
如果有某次遞減的(比如 5 3 6 的3,這樣在3入棧前就把5給彈出並計算了,所以棧裡剩3和6,而不會有5和3同時出現)
為了避開 2 1 2 ( 答案是3) 這種比 1 矮的柱子不存在導致沒被計算的情況,我們在柱子最前面和最後面都加一個0高度的柱子,這樣1這個就能被計算到,高度為1,寬度為 虛擬的4號 - 虛擬的0號 - 1 ,即面積為3。
柱高: 0 2 1 2 0
下標: 0 1 2 3 4
為了避免特判棧為空的情況,給棧也壓入一個0元素
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <stack>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
#define clr( a , x ) memset ( a , x , sizeof (a) );
#define RE freopen("1.in","r",stdin);
const int maxn = 100000 + 5;
int h[maxn];
int main() {
int n;
while (scanf("%d",&n)!=EOF,n) {
for (int i = 1; i <= n; ++i) {
scanf("%d",&h[i]);
}
stack<int>s;
ll ans = 0;
s.push(0);
h[0] = 0;
h[++n] = 0;
for (int i = 1; i <= n; ++i) {
while (h[i] < h[s.top()]) {
ll height = h[s.top()];s.pop();
ll width = i - s.top() - 1; //這裡的top是經過pop的,棧棧頂元素
if (width * height > ans) {
ans = width * height;
}
}
s.push(i);
}
cout << ans << endl;
}
return 0;
}
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
if(n==0) return 0;
int ans = 0;
stack<int>s;
s.push(0);
heights.insert(heights.begin(),-1);
heights.push_back(0);
n++;
for (int i = 1; i <= n; ++i) {
while (heights[i] < heights[s.top()]) {
int a = heights[s.top()];s.pop();
int b = i - s.top() - 1;
if (a * b > ans) {
ans = a * b;
}
}
s.push(i);
}
return ans;
}
};
二維的情況
0 0 0 0
0 1 1 0
0 1 1 0
0 0 0 0
我們把二維轉為一維,如圖我們從最後行看的話等價於
0 2 2 0
這就成一維了,再進一步,我們需要在每行都這麼幹,然後就ok了。
先對每行的每列求出該列之上1的個數(如上),遍歷每一行,按一維的情況去處理就行了。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <stack>
#include <iostream>
#include <algorithm>
using namespace std;
#define clr( a , x ) memset ( a , x , sizeof (a) );
#define RE freopen("1.in","r",stdin);
const int maxn = 2005;
int n,m;
int arr[maxn][maxn],data[maxn][maxn];
int solve(int row){
stack<int>s;
int ans = 0;
s.push(0);
data[row][0] = 0;
data[row][m+1] = 0;
for (int j = 1; j <= m+1; ++j) {
while (data[row][j] < data[row][s.top()]) {
int a = data[row][s.top()]; s.pop();
int b = j - s.top() - 1;
if (a * b > ans) {
ans = a * b;
}
}
s.push(j);
}
return ans;
}
int main() {
// RE
while (scanf("%d%d",&n,&m)!=EOF) {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j){
scanf("%d",&arr[i][j]);
}
}
clr(data,0);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j){
if(arr[i][j]) data[i][j] = arr[i][j] + data[i-1][j];
else data[i][j] = 0;
}
}
int ans = 0;
for (int i = 1; i <= n; ++i){
ans = max(ans,solve(i));
}
printf("%d\n", ans);
}
return 0;
}