筆試——單調棧

學習資料:

[1] 數據結構系列/單調棧
[2] 單調棧原理及應用 詳解 附各種類型的題目練習
[3] Leetcode 單調棧問題總結(超詳細!!!)
[4] 數據結構——單調棧

單調棧的基本介紹見參考資料 [2].

單調棧的應用有下面2種基本情況:

1、給定一組數,針對每個數,尋找它和它右(左)邊第一個比它大(小)的數之間有多少個數
2、給定一組數,針對每個數,尋找它右(左)邊以最近的數字爲最小值(最大值)的最長單調遞增(遞減)序列有多少個數

這兩種情況的模板是類似的。

題型一

POJ 3250 牛的視野

鏈接:POJ 3250 牛的視野

資料 [1] 中有POJ 3250的參考答案,但是這篇博客對於POJ 3250描述不夠準確,應該改爲:

一羣高度不完全相同的牛從左到右站成一排,每頭牛隻能看見它右邊的比它矮的牛的髮型,若遇到一頭高度大於或等於它的牛,則無法繼續看到這頭牛後面的其他牛(即使它們也比它矮)。給出每頭牛的身高,求每頭牛能看到的牛的總數。

import sys

for n in sys.stdin:
    n = int(n)
    nums = [int(input().strip()) for _ in range(n)]
    nums.append(float('inf'))  # 在末尾添加一個無窮高的牛
    stack = []
    res = [0] * (n+1)  # res[i] 表示第 i 頭牛往右能看到多少頭牛
    for i in range(n, -1, -1):
        while len(stack) > 0 and nums[i] >= nums[stack[-1]]:
            stack.pop()  # 把nums[i]右邊比它矮的或一樣高的出棧
		# 此時如果棧非空,說明棧頂就是右邊第一個比nums[i]高的牛。這之間牛的個數即爲下標之差減一
        res[i] = stack[-1] - i -1 if len(stack) > 0 else 0
        stack.append(i)
    # 如果問每一頭牛能看到多少頭牛,則返回res[:-1]。注意不需要最後一頭無窮高的牛
    # 但是本題問所有牛一共能看到多少頭,所以求和。
    print(sum(res[:-1]))

美團點評2020校招數據分析方向筆試題——比大小

鏈接:美團點評2020校招數據分析方向筆試題——比大小

在這裏插入圖片描述在這裏插入圖片描述

分析:本題與上一題的不同之處在於:

1、如果一個數右邊沒有比它大的數,則不必記錄差值,保留爲-1即可。故不需要加一個無窮大。

2、根據題意,統計的是 stack[-1] - i 而不是 stack[-1] - i -1(因爲要包括那個第一個比它高的數)

參考答案:

import sys

for n in sys.stdin:
    n = int(n)
    nums = [int(input().strip()) for _ in range(n)]
    stack = []
    res = [-1] * n
    for i in range(n - 1, -1, -1):  # 不需要加無窮大,從n-1而不是n開始倒序遍歷
        while len(stack) > 0 and nums[i] >= nums[stack[-1]]:
            stack.pop()
        res[i] = stack[-1] - i if len(stack) > 0 else -1  # 下標之差不需要減一
        stack.append(i)
    print('\n'.join([str(x) for x in res]))

LC739. 每日溫度

在這裏插入圖片描述
題目鏈接:https://leetcode-cn.com/problems/daily-temperatures/

本題基本與前一題相同

class Solution:
    def dailyTemperatures(self, T: List[int]) -> List[int]:
        n=len(T)
        stack=[]
        res=[0]*n
        for i in range(n-1,-1,-1):
            while len(stack)>0 and T[stack[-1]]<=T[i]:
                stack.pop()
            res[i]=stack[-1]-i if len(stack)>0 else 0
            stack.append(i)
        return res

環形排列

在上一題的基礎上:如果這些數圍成一個圈,求往順時針方向看多少步纔到達第一個比它高的(如果沒有比它高的,則對應-1)。

思路:

增加了環形屬性後,問題的難點在於:“下一個” 的意義不僅僅是當前元素的右邊了,有可能出現在當前元素的左邊。

解決思路爲:將原始數組“翻倍”,就是在後面再接一個原始數組,這樣的話,按照之前“比身高”的流程,每個元素不僅可以比較自己右邊的元素,而且也可以和左邊的元素比較了。

在這裏插入圖片描述

import sys

for n in sys.stdin:
    n = int(n)
    nums = [int(input().strip()) for _ in range(n)] * 2  # 再接一個一樣的數組
    stack = []
    res = [-1] * 2 * n
    for i in range(2 * n - 1, -1, -1):  # n-1 修改爲 2n-1
        while len(stack) > 0 and nums[i] >= nums[stack[-1]]:
            stack.pop()
        res[i] = stack[-1] - i if len(stack) > 0 else -1
        stack.append(i)
    print('\n'.join([str(x) for x in res[:n]]))  # 注意最後只需要前 n 個的結果

LC84. Largest Rectangle in Histogram

題目鏈接:84. 柱狀圖中最大的矩形

在這裏插入圖片描述在這裏插入圖片描述

先逆序遍歷,找到向右第一個比它矮的柱子,再順序遍歷,找到向左第一個比它矮的柱子,算出中間有多少柱子即爲寬度,然後寬度乘以高即爲面積。

如果左邊或右邊沒有比它矮的柱子,就沒辦法求出它們中間有多少比它高(或一樣高)的柱子,所以類似於“POJ 3250 牛的視野”,此處需要分別在開頭和末尾插入一個最矮的數0。

class Solution:
    def largestRectangleArea(self, heights) -> int:
        n = len(heights)
        if n==0:return 0
        # 開頭和末尾都要插入一個最矮的數
        heights.insert(0, 0)
        heights.append(0)
        stack = []
        right_look = [0] * (n + 2)
        for i in range(n + 1, -1, -1):
            while len(stack) > 0 and heights[i] <= heights[stack[-1]]:
                stack.pop()
            right_look[i] = stack[-1] - i - 1 if len(stack) > 0 else 0
            stack.append(i)
        stack.clear()
        res = [x for x in right_look]
        for i in range(n + 2):
            while len(stack) > 0 and heights[i] <= heights[stack[-1]]:
                stack.pop()
            res[i] += (i - stack[-1] - 1 if len(stack) > 0 else 0) + 1  # 先求出矩形的寬度(往左+往右+自己)
            res[i] *= heights[i]  # 寬度乘以高
            stack.append(i)
        return max(res[1:-1])  # 去掉加入的開頭和末尾的0對應的值

求最大區間

題目來自文章開頭寫的參考資料 [3]

在這裏插入圖片描述
本題與上一題LC84類似,但要注意區間的範圍如何用切片來表達。

class Solution:
    def largestRectangleArea(self, heights) -> int:
        n = len(heights)
        heights.insert(0, 0)
        heights.append(0)
        stack = []
        right_look = [0] * (n + 2)
        for i in range(n + 1, -1, -1):
            while len(stack) > 0 and heights[i] <= heights[stack[-1]]:
                stack.pop()
            # 區間 [i+1, stack[-1]-1] 的和,即不包括柱子i和柱子stack[-1]
            right_look[i] = sum(heights[i + 1:stack[-1]]) if len(stack) > 0 else 0
            stack.append(i)
        stack.clear()
        res = [x for x in right_look]
        for i in range(n + 2):
            while len(stack) > 0 and heights[i] <= heights[stack[-1]]:
                stack.pop()
            # 區間 [stack[-1]+1, i-1] 的和,即不包括柱子i和柱子stack[-1]
            res[i] += (sum(heights[stack[-1] + 1:i]) if len(stack) > 0 else 0) + heights[i]  # 先求出總區間的和(注意加上柱子i)
            res[i] *= heights[i]  # 區間的和乘以最小值
            stack.append(i)
        return max(res[1:-1])

題型二

騰訊2020校園招聘編程題——逛街

鏈接:騰訊2020校園招聘編程題——逛街

在這裏插入圖片描述

在這裏插入圖片描述

注意,題型一的棧中需要保存下標,因爲需要在出棧時通過下標相減來得到距離

而在題型二,棧中沒必要保存下標,因爲每個位置對應的結果是 棧的元素個數 而不是 棧頂元素與當前元素的位置差值

import sys
# 本答案參考自評論區
def parse_nums(nums_str):
    return [int(x) for x in nums_str.strip().split()]

for n in sys.stdin:
    n = int(n)
    nums = parse_nums(input())
    assert n == len(nums)
    right_look = [0] * n
    stack = []
    # 從右往左遍歷,統計每個位置往右看能看到多少棟樓(往右的高度單調遞增)
    # 注意對比題型一,記錄結果的語句提到了while前,且記錄的是棧的元素個數
    for i in range(n - 1, -1, -1):
        right_look[i] = len(stack)  # 此時棧中的元素個數即爲右邊最長的單調遞增序列的長度
        while len(stack) != 0 and nums[i] >= nums[stack[-1]]:
            stack.pop()
        stack.append(i)
    stack.clear()
    # 從左往右遍歷,統計每個位置往左看能看到多少棟樓(往左的高度單調遞增)
    both_look = [x for x in right_look]
    for i in range(n):
        both_look[i] += len(stack)+1  # 能看到樓的總數爲往右+往左+自己
        while len(stack) != 0 and nums[i] >= nums[stack[-1]]:
            stack.pop()
        stack.append(i)
    both_look = [str(x) for x in both_look]
    print(' '.join(both_look))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章