單調棧進階-接雨水-最大矩形

tfzh發表於2022-05-09

1 前言

在前陣子的一篇分享裡,簡單提到了單調棧這個資料結構,文章如下↓↓↓

分享一個簡單但挺有意思的演算法題2-貪心-單調棧-動態規劃

當時只是用單調棧解決了股票問題,是最基礎的入門示例,算是easy或者勉強medium級別,今天用單調棧來解決一些hard題目

2 示例-接雨水

42. 接雨水

這是一道經典的面試題,解法有三種,一是正反遍歷求出每個點的左右最大高度,勉強算動態規劃;二是單調棧;三是雙指標;其中效率最高的是雙指標,最低的是正反遍歷,單調棧效率和雙指標是一樣的,只是多使用了一個棧結構,故而空間複雜度不是最優。

2.1 單調棧的思路

不難想到,每一個點能接到的雨水,取決於該點左右兩邊的最大高度,兩者之間的最小值即雨水的最大高度,方法一的正反遍歷也是為了求取左右兩邊的最大高度,優點是直觀好理解,缺點是兩次遍歷,效率較低。

而仔細思考的話,使用單調棧,維護一個遞減的單調棧,也能求取該點左右兩邊的最大高度,只是不是針對每一個點,而是每一個上坡,只要有上坡,就彈出,並計算雨水;

具體思路是:遇到一個小於棧頂的元素,則入棧,遇到一個大於棧頂的元素,且此時棧非空,則彈出棧頂,此時計算寬度為i - 已彈出的棧頂 - 1,高度為min(height[i], 最新棧頂的高度) - 已彈出的棧頂

2.2 程式碼

var trap = function(height) {
    let stack = [];
    let len   = height.length
    let res   = 0;
    for(let i=0;i < len;i++){
        while(stack.length > 0 && height[i] > height[stack[stack.length - 1]]){
            let top = height[stack.pop()]
            if(stack.length == 0){
                break
            }
            let width_ = i - stack[stack.length - 1] - 1
            let height_= Math.min(height[i], height[stack[stack.length - 1]]) - top
            res += width_*height_
        }
        stack.push(i)
    }
    return res
};

image.png

3 示例-柱狀圖中的最大矩形

84. 柱狀圖中最大的矩形

3.1 單調棧的思路

這個跟上一題的思路基本一致,只是這裡變成了維護一個單調遞增棧,遇到一個大於棧頂的元素就入棧,遇到一個小於棧頂的元素,說明上坡結束,需要一次彈出棧頂,計算最大面積了,此時高度為剛剛彈出的棧頂對應的高度,寬度為i - 最新棧頂下標 - 1(PS:寬度不能是i - 剛剛彈出的棧頂對應的下標,因為heights[i]可能連續小於最新棧頂,此時寬度會連續橫向拓寬)

3.2 程式碼

var largestRectangleArea = function(heights) {
    heights.push(0)
    heights.unshift(0)
    let stack = []
    let len = heights.length
    let res = 0
    for(let i=0;i < len;i++){
        while(stack.length > 0 && heights[i] < heights[stack[stack.length - 1]]){
            //吐出來個
            let top_i  = stack.pop(stack)
            let height = heights[top_i]
            let width  = i - stack[stack.length - 1] - 1
            res = Math.max(res, height*width)
        }
        stack.push(i)
    }
    return res
};

image.png

4 示例-最大矩形

85. 最大矩形

4.1 單調棧思路

這道題基本和柱狀圖中的最大矩形沒有啥區別,柱狀圖中的最大矩形是計算了一層,這個是計算多層,我們把每一行都當做一個柱狀圖,求出每一層的最大矩形,其中最大值就是最終答案。

matrix = [
    ["1","0","1","0","0"],
    ["1","0","1","1","1"],
    ["1","1","1","1","1"],
    ["1","0","0","1","0"]
]
//轉化成柱狀圖後是
heights = [
    [ 1 , 0 , 1 , 0 , 0 ],
    [ 2 , 0 , 2 , 1 , 1 ],
    [ 3 , 1 , 3 , 2 , 2 ],
    [ 4 , 0 , 0 , 3 , 0 ]
]

heights遍歷,求取每一行的最大矩形即可

4.2 程式碼

var largestRectangleArea = function(heights) {
    heights.push(0)
    heights.unshift(0)
    let stack = []
    let len = heights.length
    let res = 0
    for(let i=0;i < len;i++){
        while(stack.length > 0 && heights[i] < heights[stack[stack.length - 1]]){
            //吐出來個
            let top_i  = stack.pop(stack)
            let height = heights[top_i]
            let weight = i - stack[stack.length - 1] - 1
            res = Math.max(res, height*weight)
        }
        stack.push(i)
    }
    return res
};
var maximalRectangle = function(matrix) {
    let h    = matrix.length
    let w    = matrix[0].length
    let tree = [];
    let res  = 0;
    for (let i = 0; i < h; i++) {
        tree[i] = new Array(w)
        for (let j = 0; j < w; j++) {
            //樹的高度
            if (matrix[i][j] == '0') {
                tree[i][j] = 0
            }else{
                tree[i][j] = i > 0 ? tree[i-1][j] + 1 : 1
            }
        }
        let this_line = largestRectangleArea(tree[i].concat())//這裡因為js的天生短板,要使用深拷貝
        res = Math.max(res, this_line)
    }
    return res
};

image.png

相關文章