<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ?LeetCode刷題實戰(zhàn)84: 柱狀圖中最大的矩形

          共 3805字,需瀏覽 8分鐘

           ·

          2020-11-03 09:50

          算法的重要性,我就不多說了吧,想去大廠,就必須要經(jīng)過基礎(chǔ)知識和業(yè)務(wù)邏輯面試+算法面試。所以,為了提高大家的算法能力,這個公眾號后續(xù)每天帶大家做一道算法題,題目就從LeetCode上面選 !

          今天和大家聊的問題叫做?柱狀圖中最大的矩形,我們先來看題面:

          https://leetcode-cn.com/problems/largest-rectangle-in-histogram/

          Given n non-negative integers representing the histogram's bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.

          題意


          給定 n 個非負(fù)整數(shù),用來表示柱狀圖中各個柱子的高度。每個柱子彼此相鄰,且寬度為 1 。
          求在該柱狀圖中,能夠勾勒出來的矩形的最大面積。
          樣例

          輸入: [2,1,5,6,2,3]
          輸出: 10


          解題

          https://www.cnblogs.com/techflow/p/13304195.html

          區(qū)間求最值

          拿到手應(yīng)該能感受到這題的難度,我們一上來的確沒有什么太好的思路,題目也比較明確,沒有太多可以分析的入手點。所以我們可以先來思考一下最簡單的解法。
          最簡單的解法就是找出能夠圍成的所有矩形,然后比較它們之間的面積,得出其中的最大面積。我們很容易可以想到可以遍歷矩形的起始位置,這樣就得到了矩形的寬。至于矩形的長也很簡單,就是選定的這個區(qū)間段里的最低高度
          我們可以做一個小小的思路轉(zhuǎn)換,假設(shè)這些矩形都是木條,我們是要選出木條來制作木桶。那么根據(jù)木桶效應(yīng),木桶圍成的水的高度取決于最短的那根木條,同樣圍成矩形的面積的高取決于這些矩形當(dāng)中最矮的那個。也就是說,當(dāng)我們確定了區(qū)間之后,我們只需要找到區(qū)間里最小的數(shù)就可以了。所以這題就轉(zhuǎn)化成了區(qū)間求最值的問題,比如上圖當(dāng)中,如果我們選擇最后三個矩形,那么它的高度就是2。
          我們假設(shè)一共有n個長條矩形可供選擇,那么我們可以選出的首尾組合就是,大概是n的平方量級個區(qū)間。對于每個區(qū)間,我們需要遍歷它們中的元素獲取最小值,這需要的遍歷時間,所以整體的復(fù)雜度應(yīng)該在量級。顯然這是一個非常大的數(shù)量級,當(dāng)n超過1000就很難計算出解了。
          這個思路顯然不夠好,我們想要對它進(jìn)行優(yōu)化也不容易。比如說如果你學(xué)過線段樹這類的數(shù)據(jù)結(jié)構(gòu),可能還會想到使用線段樹,我們可以將每次求最小值的查詢優(yōu)化到,但即便如此最終的復(fù)雜度也很高。這是因為我們遍歷區(qū)間首尾位置就耗費了,而這是很難優(yōu)化的。所以這個思路的極限已經(jīng)確定了,我們無法做出大的優(yōu)化
          從這點出發(fā),如果存在更好的解法,那么一定不是通過這種方式進(jìn)行的。

          逆向思維

          上面的一種思路雖然不太可行,但是它提供了一種正向思路。我們搜索所有的區(qū)間,然后通過區(qū)間里的木條確定區(qū)圍成矩形的高度,就得到了矩形的面積。
          既然這條路走不通,我們能不能反向思考呢?我們假設(shè)我們找到了答案,它是區(qū)間[a, b]段的木條圍成的矩形,它的高度是h。那么根據(jù)木桶效應(yīng),a到b區(qū)間段的木條當(dāng)中一定有一根的長度是h。比如下圖當(dāng)中[5, 6, 2, 3]如果要圍成矩形,那么高度只能是2。
          既然如此,我們可以尋找以某根木條為短板所能構(gòu)成的最大矩形。比如上圖當(dāng)中,如果我們以第一根木條去尋找,就只能找到它本身,所以這個矩形的面積就是1 x 2 = 2。如果以第二根木條為短板去尋找,可以找到整個區(qū)間,它對應(yīng)的面積就是1 x 6 = 6。
          因為我們只有n個木條,以每個木條為短板尋找最大矩形,那么我們一定可以找出最多n個矩形。最終的答案一定在這n個矩形當(dāng)中,在正向思維當(dāng)中我們尋找木條區(qū)間需要的復(fù)雜度,然而我們尋找短板,只需要,也就是說這種思路的搜索空間更小,只要我們保證搜索的效率,就可以更快地找到答案。
          為了找到每個木條對應(yīng)的最大矩形,我們需要找到每個短板向左以及向右能夠延伸到的最遠(yuǎn)位置。比如上圖例子當(dāng)中,根據(jù)每個木條向右延伸的最遠(yuǎn)位置,我們可以得到[0, 5, 3, 3, 5, 5],同樣,我們可以得到每根木條向左延伸的數(shù)組:[0, 0, 2, 3, 2, 5]。有了這兩個數(shù)組之后,我們就可以計算出以每一根木條為短板的最大矩形的面積,在這其中面積最大的那個就是答案。
          這個位置我們可以使用單調(diào)棧來求,我們用一個有序的棧來維護(hù)延伸的位置。舉個例子,我們用從棧底往棧頂遞增的單調(diào)棧來維護(hù)每根木條向右延伸的位置。當(dāng)我們遇到一根新的木條時,會彈出棧中所有比它長的值。對于這些值來說,這根新的木條就是它的右邊界。比如[5, 6, 2],一開始讀到5,入棧。接著讀到6,由于6大于棧頂?shù)?,所以6入棧。最后讀到2,由于2比6小,所以6出棧,對于6來說,2的位置就是它的右側(cè)邊界。正是由于2比它小,所以它才需要出棧,也說明了2的左側(cè)的元素都比6來的大,否則6在之前就應(yīng)該出棧了。同理,2也是5的右側(cè)邊界。
          我們把以上的邏輯翻轉(zhuǎn),就得到了左側(cè)邊界求解的邏輯。左右邊界有了之后,我們只需要乘上它們之間的區(qū)間長度就得到了矩形的面積。
          接著,我們來寫出代碼:

          class Solution:
          ????def largestRectangleArea(self, heights:?List[int]) -> int:
          ????????n = len(heights)
          ??# 左側(cè)邊界初始化為0
          ????????left_side = [0?for?i in range(n)]
          ????????# 右側(cè)邊界初始化為n-1
          ????????right_side = [n-1?for?_ in range(n)]
          ????????
          ????????stack_left = []
          ????????stack_right = []
          ????????
          ????????for?i in range(n):
          ????????????h = heights[i]
          ????????????# 彈出棧中所有比當(dāng)前元素小的值
          ????????????# 注意,棧內(nèi)存儲的是下標(biāo)
          ????????????while?len(stack_right) > 0?and?h < heights[stack_right[-1]]:
          ????????????????tail = stack_right[-1]
          ????????????????stack_right.pop()
          ????????????????right_side[tail] = i - 1
          ????????????
          ????????????# 當(dāng)前元素入棧
          ????????????stack_right.append(i)
          ????????????
          ????????????# 把坐標(biāo)翻轉(zhuǎn),等價于逆向遍歷
          ????????????i_ = n - 1?- i
          ????????????h = heights[i_]
          ????????????
          ????????????# 維護(hù)單調(diào)棧的邏輯同上
          ????????????while?len(stack_left) > 0?and?h < heights[stack_left[-1]]:
          ????????????????tail = stack_left[-1]
          ????????????????stack_left.pop()
          ????????????????left_side[tail] = i_ + 1
          ????????????????
          ????????????# 當(dāng)前元素入棧
          ????????????stack_left.append(i_)
          ????????????????

          ????????ret?= 0
          ????????for?i in range(n):
          ????????????# 矩形面積等于右側(cè)邊界-左側(cè)邊界+1?x?高度
          ????????????cur = (right_side[i] - left_side[i] + 1) * heights[i]
          ????????????ret?= max(ret, cur)
          ????????return?ret


          總結(jié)

          想要把這道題做出來,單單理清楚題意和單單會單調(diào)棧都是沒有用的。既需要理清楚題意,從最簡單的解法出發(fā)推導(dǎo)出優(yōu)化的方法,也需要深刻理解單調(diào)棧這個數(shù)據(jù)結(jié)構(gòu),才可以靈活應(yīng)用。
          另外,在代碼當(dāng)中需要特別注意邊界的情況。比如初始化時左右邊界的設(shè)定,以及可能會出現(xiàn)連續(xù)相等元素的情況,這些都需要納入考慮。代碼雖然看起來簡單,但是隱藏了很多細(xì)節(jié),所以只看代碼是沒用的,最好還是能親自實現(xiàn)一下。
          好了,今天的文章就到這里,如果覺得有所收獲,請順手點個在看或者轉(zhuǎn)發(fā)吧,你們的支持是我最大的動力。


          上期推文:

          LeetCode50-80題匯總,速度收藏!
          LeetCode刷題實戰(zhàn)81:搜索旋轉(zhuǎn)排序數(shù)組 II
          LeetCode刷題實戰(zhàn)82:刪除排序鏈表中的重復(fù)元素 II
          LeetCode刷題實戰(zhàn)83:刪除排序鏈表中的重復(fù)元素

          瀏覽 67
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  好想操骚逼无码视频 | 国产精品久久久久久久毛片明星 | 免费①区二区三区四区 | 免费又黄又爽又色的视频 | 手机免费在线看片网址av |