單調隊列之廣告印刷問題

問題:有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中剩餘的所有元素均小於等於其後的所有元素

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章