問題:有n幢建築,其高度分別爲H1,...,Hn,其寬度爲1,且這些建築緊靠在一起,當前需要在這些建築上刷一個非常大的矩形廣告,求矩形廣告的最大值。
先翻譯成數學題,給定n個正數的序列,定義區間值A(i,j)= Min(Hi,Hi+1,..,Hj) * (j-i+1),求所有區間值的最大值。這個問題網上都有答案,無奈答案一律都相當簡單,個人實在無法理解,故成此文!
(1) 最簡單的方法
直接查看所有區間的區間值,複雜度爲O(n^3),如下爲示例代碼:
static int get_min(int *s, int start, int end)
{
int i, min = s[start];
for (i = start;i <= end;i++) {
if (min > s[i])
min = s[i];
}
return min;
}
int continous_sum_o3(int *s, int n)
{
int i, j;
int min, max = s[0];
for (i = 0;i <= n - 1;i++) {
for (j = i;j <= n - 1;j++) {
min = get_min(s, i, j);
if (max < min * (j - i + 1))
max = min * (j - i + 1);
}
}
return max;
}
(2) 使用O(1)求區間最小數
#define MIN(x, y) (((x)<(y)) ? (x) : (y))
int continous_sum_o2(int *s, int n)
{
int i, j;
int min, max = s[0];
for (i = 0;i <= n - 1;i++) {
min = s[i];
if (max < s[i])
max = s[i];
for (j = i + 1;j <= n - 1;j++) {
min = MIN(min, s[j]);
if (max < min * (j - i + 1))
max = min * (j - i + 1);
}
}
return max;
}
(3) 使用單調隊列
unsigned int continous_sum_pq(int *s, int n)
{
int i, max, val;
int start = 0, end = -1;
int *rn = NULL, *ln = NULL, *P = NULL;
rn = malloc(sizeof(unsigned int) * n);
ln = malloc(sizeof(unsigned int) * n);
P = malloc(sizeof(unsigned int) * n);
/* calc right continuous block */
for (i = 0;i < n;i++) {
for (;end >= start && s[i] < s[P[end]];end--) {
rn[P[end]] = i;
}
end++;
P[end] = i;
}
for (i = start;i <= end;i++) {
rn[P[i]] = n;
}
/* calc right continuous block */
for (i = n - 1;i >= 0;i--) {
for (;end >= start && s[i] < s[P[end]];end--) {
ln[P[end]] = i;
}
end++;
P[end] = i;
}
for (i = start;i <= end;i++) {
ln[P[i]] = -1;
}
/* determin the max block */
max = s[0];
for (i = 0;i < n;i++) {
val = s[i] * (rn[i] - ln[i] - 1);
if (val > max)
max = val;
}
#ifdef DEBUG
printf("the right continous block is\n");
for (i = 0;i < n;i++) {
printf("%d ", rn[i]);
}
printf("\n");
printf("the left continous block is\n");
for (i = 0;i < n;i++) {
printf("%d ", ln[i]);
}
printf("\n");
#endif
return max;
}
思路:
區間最小數肯定是區間內的某個數,考察某一個數往左、右兩個方向可延伸的最大長度,如此可以求得以這個數作爲區間最小數的區間值。
往右延伸,即從此數開始,右邊的若干連續數均大於等於當前數,找到這個延伸的最大長度,注意代碼中當某個數出單調隊列時才計算此數的延伸長度。
求得往左、往右延伸的長度,再相加即得此數作爲區間最小數時的區間值
代碼細節:
使用數組rn 存儲某個數向右擴展的最大下標,即對於某個數H[i]來說,rn[i] 存儲的是一個數組下標,從i到rn[i](不含rnr[i])的所有數均大於等於H[i]。
數組P 存儲原數組的下標,對於任意的 i<j,且i,j均屬於P,則有H[i] <= H[j],即P是一個單調隊列。每次檢查一個數H[i]時,將此數於P數組中的比較,從隊尾開始比較,將所有大於H[i]的數出隊列,並計算出隊列元素的延伸長度,即rn值。檢查完原數組中所有元素時,將數組P 中的剩餘元素的rn值均設置爲n,即P中剩餘的所有元素均小於等於其後的所有元素