问题:有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中剩余的所有元素均小于等于其后的所有元素