单调队列之广告印刷问题

问题:有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)求区间最小数

此方法复杂度为O(n^2),如下是示例代码:

#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) 使用单调队列

此方法复杂度为O(n),如下是示例代码:
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中剩余的所有元素均小于等于其后的所有元素

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值