Lettcode----84. 柱狀圖中最大的矩形

在這裏插入圖片描述
方法一:暴力

遍歷所有柱子,對於一根柱子將它向左右擴展算出面積,若比最大面積大則更新最大面積。時間複雜度O(n2)O(n^2)

int largestRectangleArea(vector<int>& heights) {
    int len = heights.size();
    if(len == 0)
        return 0;
    int ans = 0;
    for(int i = 0;i < len;i++)
    {
        int now_num = heights[i];
        int num = 0;
        for(int j = 0;j < len;j++)
        {
            if(heights[j] >= now_num)		//可以擴展
                num += now_num;
            else{							//無法擴展停下
                ans = max(ans,num);
                num = 0;
            }
        }
        ans = max(ans,num);
    }
    return ans;
}

方法二:分治

上述方法複雜度爲O(n2)O(n^2),會超時。我們每次可以找到區間內的最小柱子,其所能勾勒出的面積爲柱子的高度乘以區間長度。每找到一個柱子可以將其左右劃分爲兩個子子問題,採用分治的思想解決問題。平均複雜度爲O(nlogn)O(nlogn),但是當數組爲升序或者降序時複雜度還是爲O(n2)O(n^2)

題目所給函數的返回值爲int,但是heights[mid]*(r-l+1)這個式子居然會爆int,需要將其改爲long long int,而且c++自帶的max函數的參數爲int類型,我們還需要自己寫一個比較。如果嫌上述操作麻煩我們可以用java代碼來實現。

c++版

int largestRectangleArea(vector<int>& heights) {
    int len = heights.size();
    if(len == 0)
        return 0;
    return Binary_search(0,len-1,heights);
}
int Binary_search(int l,int r,vector<int>& heights)
{
    if(l > r)
        return 0;
    int mid = l;
    for(int i = l;i <= r;i++)
    {
        if(heights[mid] > heights[i])
        {
            mid = i;
        }
    }
    long long int a = heights[mid]*(r-l+1);
    long long int b = max(Binary_search(mid+1,r,heights),Binary_search(l,mid-1,heights));
    if(a < b)
    {
        long long int t = a;
        a = b;
        b = t;
    }
    return a;
}

java版

public int largestRectangleArea(int[] heights) {
    int len = heights.length;
    if(len == 0)
        return 0;
    return Binary_search(0,len-1,heights);
}
public int Binary_search(int l,int r,int[] heights)
{
    if(l > r)
        return 0;
    int mid = l;
    for(int i = l;i <= r;i++)
    {
        if(heights[mid] > heights[i])
        {
            mid = i;
        }
    }
    return Math.max(heights[mid]*(r-l+1),Math.max(Binary_search(mid+1,r,heights),Binary_search(l,mid-1,heights)));
}

方法三:分治+線段樹

方法二最壞的時間複雜度爲O(n2)O(n^2),因爲分治最壞情況是O(n)O(n),而且我們每次查詢一個區間的最小值的時間複雜度爲O(n)O(n),當我們看到區間查詢就能想到線段樹了,我們通過線段樹來維護區間的最小值,就可以將查詢的複雜度降爲O(logn)O(logn)了,所以我們算法的最壞時間複雜度爲O(nlongn)O(nlongn)

    int tree[100000*3];
    int cnt,min_num,indx;
    int largestRectangleArea(vector<int>& heights) {
        int len = heights.size();
        if(len == 0)
            return 0;
        cnt = 0;
        create_tree(0,len - 1,1,heights);       //建樹
        return Binary_search(0,len-1,heights);
    }
    int Binary_search(int l,int r,vector<int>& heights)     //分治
    {
        if(l > r)
            return 0;
        min_num = heights[l];
        indx = l;
        find(l,r,0,heights.size()-1,1,heights);
        int mid = indx;     //由於indx爲全局變量,防止後面的操作改變其值,用mid接收它
        long long int b = max(Binary_search(mid+1,r,heights),Binary_search(l,mid-1,heights));
        long long int a = (long long int)heights[mid]*(r-l+1);
        if(a < b)
        {
            long long int t = a;
            a = b;
            b = t;
        }
        return a;
    }
    void updata(int k,vector<int>& heights)     //維護線段樹
    {
        //父節點的值爲左兒子和右兒子的最小值
        if(heights[tree[k << 1]] < heights[tree[(k << 1) | 1]])
        {
            tree[k] = tree[k << 1];
        }
        else tree[k] = tree[(k << 1) | 1];
    }
    void create_tree(int l,int r,int k,vector<int>& heights)        //建樹
    {
        if(r == l)
        {
            tree[k] = cnt++;
            return;
        }
        int mid = (l + r) >> 1;
        create_tree(l,mid,k << 1,heights);
        create_tree(mid+1,r,(k << 1) | 1,heights);
        updata(k,heights);
    }
    void find(int find_l,int find_r,int l,int r,int k,vector<int>& heights)//區間查詢
    {
        if(find_l <= l && r <= find_r)
        {
            if(min_num > heights[tree[k]])
            {
                min_num = heights[tree[k]];
                indx = tree[k];
            }
            return;
        }
        int mid = (l + r) >> 1;
        if(mid >= find_l) find(find_l,find_r,l,mid,k << 1,heights);
        if(find_r >= mid+1) find(find_l,find_r,mid+1,r,(k << 1) | 1,heights);
    }

方法四:單調棧
對於一個柱子我們要能確定它所能勾勒的最大面積的條件是:知道左邊和右邊比它小的柱子的位置。因爲只有碰到比它小的柱子時它才無法擴展,當它無法向左右擴展時說明此時是它所能勾勒的最大面積。方法一其實就是這個思想,但是它每次都要遍歷來尋找邊界位置,需要兩層循環,我們是否可以考慮將其邊界緩存下來,只用一次遍歷來完成操作。

我們維護一個單調棧,其單調性質爲從棧底到棧頂單調遞增(其實這樣我們就將左邊界緩存下來了)。

對於當前的一個柱子來說:
1.它比棧頂小則說明它是棧頂的右邊界,此時棧頂的最大面積已經確定可將其彈出,其最大面積的算法爲:×(1)已經彈出柱子的高度\times(當前柱子的位置-棧頂柱子的位置-1)循環上述過程直到其滿足單調棧的性質將其入棧。
2.若它比棧頂大則將其入棧。

操作到最後,棧中還會剩餘一些柱子,這時所有柱子的右邊界都可以看成最大下標+1,當再剩一個柱子時其左邊界爲-1(表示沒有柱子可擴展)。

對於棧中的元素我們需要知道其高度和位置,我們只需要將其位置存在棧中,可通過數組索引來直到高度。

上述過程自己動手畫圖過一遍就可以明白。
由於每個柱子只入棧和出棧各一次,所以時間複雜度爲O(n)O(n)

int largestRectangleArea(vector<int>& heights) {
    int len = heights.size();
    if(len == 0)
        return 0;
    int ans = 0,indx;
    stack<int>s;
    s.push(-1);//添加哨兵
    for(int i = 0;i < len;i++)
    {
    	//找到右邊界
        while(!s.empty() && s.top() >= 0 && heights[s.top()] > heights[i])
        {
            indx = s.top();
            s.pop();
            
            //將高度相同的柱子全部彈出
            while(!s.empty() && s.top() >= 0 && heights[s.top()] == heights[indx])
            {
                indx = s.top();
                s.pop();
            }
            
            if(!s.empty())
                ans = max(ans,heights[indx]*(i - s.top() - 1));
        }
        s.push(i);
    }
    while(!s.empty())
    {
        indx = s.top();
        s.pop();
        if(!s.empty())
            ans = max(ans,heights[indx]*(len - s.top() - 1));
    }
    return ans;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章