1 前言
在前陣子的一篇分享裡,簡單提到了單調棧這個資料結構,文章如下↓↓↓
當時只是用單調棧解決了股票問題,是最基礎的入門示例,算是easy
或者勉強medium
級別,今天用單調棧來解決一些hard
題目
2 示例-接雨水
這是一道經典的面試題,解法有三種,一是正反遍歷求出每個點的左右最大高度,勉強算動態規劃;二是單調棧;三是雙指標;其中效率最高的是雙指標,最低的是正反遍歷,單調棧效率和雙指標是一樣的,只是多使用了一個棧結構,故而空間複雜度不是最優。
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
};
3 示例-柱狀圖中的最大矩形
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
};
4 示例-最大矩形
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
};