单调堆栈 - 识别模式

136 阅读7分钟

[

Gul Ershad

](mailtogulershad.medium.com/?source=pos…)

Gul Ershad

关注

6月17日

-

11分钟阅读

[

拯救

](medium.com/m/signin?ac…)

单调堆栈 - 识别模式

简介

单调堆栈是一个堆栈,其元素是单调增加或减少的。它包含了一个典型堆栈所具有的所有品质,其元素都是单调递减或递增。

以下是单调堆栈的特点。

  • 它是一个数组情况下的查询范围
  • 最小值/最大值的元素
  • 当一个元素从单调堆栈中被弹出时,它将永远不会再被利用。

单调堆栈问题主要是上一个/下一个小/大问题。它在保持单调性的同时,当一个新的项目被推入堆栈时,会弹出元素。

一个单调的堆栈看起来像下面这样。

图1:单调性堆栈

单调堆栈的基本伪代码。

图2:单调堆栈

单调堆栈的概念。

不断推送 堆栈中的元素,直到它在一个比堆栈顶部小的元素上,当它达到这样一个数字时,不断弹出堆栈,直到它是空的 或者它的顶部 比当前元素小的地方。因此,堆栈中的所有元素都是按从下到上的顺序递增。

图3:单调递增堆栈

函数的单调性

如果一个函数在其整个领域内不是增加就是减少,那么它就被称为单调性。

一个现实世界中的单调性函数的例子。

  • 一个生命体的年龄是一个在时间上单调增加的函数。而生命中剩下的时间是一个在时间上单调递减的函数。

单调递增函数。

如果x1<x2和f(x1)<f(x2),那么函数被称为递增函数。

图3:单调递增函数

单调递减函数。

如果 x1 < x2 并且 f(x1) > f(x2),那么函数被称为递减函数。

图4:单调递减函数

单调堆栈相关问题及解决方案

以下是与单调堆栈有关的问题列表。

下一个大元素 I

在一个数组中,某个元素x下一个大元素是同一数组中x 右边第一个大元素。

给你两个不同的以0为索引的整数数组nums1nums2 ,其中nums1nums2 的一个子集。

对于每个0 <= i < nums1.length ,找到索引j ,使nums1[i] == nums2[j] ,并确定nums2nums2[j]下一个大元素。如果没有下一个大元素,那么这个查询的答案是-1

返回一个 长度为 nums1.length数组 ans ,这样, ans[i]下一个更大的元素 如上所述。

Input: nums1 = [4,1,2], nums2 = [1,3,4,2]Output: [-1,3,-1]

解决方案

class Solution(object):    def nextGreaterElement(self, nums1, nums2):        """        :type nums1: List[int]        :type nums2: List[int]        :rtype: List[int]        """        stack = []        d = {}                for i in range(len(nums2)):            d[nums2[i]] = -1                    for i in range(len(nums2)):            while len(stack) > 0 and nums2[i] > stack[-1]:                item = stack.pop()                d[item] = nums2[i]                            stack.append(nums2[i])                    for i in range(len(nums1)):            nums1[i] = d[nums1[i]]                return nums1

下一个大元素II

给定一个循环整数数组nums (即nums[nums.length - 1] 的下一个元素是nums[0] ),返回 下一个大数 nums 中的每个元素

一个数字x下一个大数是它在数组中下一个遍历顺序的第一个大数,这意味着你可以循环搜索,找到它的下一个大数。如果它不存在,则返回-1 这个数字。

Input: nums = [1,2,1]Output: [2,-1,2]

解决方案

class Solution(object):    def nextGreaterElements(self, nums):        """        :type nums: List[int]        :rtype: List[int]        """        n = len(nums)        stack = []        result = [-1] * n                for i in range(2*n-1):            index = i % n                        while stack and stack[-1][1] < nums[index]:                resIndex, _ = stack.pop()                result[resIndex] = nums[index]                        stack.append([index, nums[index]])                return result

使数组不递减的步骤

给你一个以0为索引的整数数组nums 。在一个步骤中,移除所有元素nums[i] ,其中nums[i - 1] > nums[i] 为所有0 < i < nums.length

返回所执行的步骤的数量,直到 nums 成为一个 非递减 数组

Input: nums = [5,3,4,4,7,3,6,11,8,5,11]Output: 3

解决方案

class Solution(object):    def totalSteps(self, nums):        """        :type nums: List[int]        :rtype: int        """        n = len(nums)        dp = [0] * n        res = 0        stack = []            for i in range(n-1, -1, -1):            while stack and nums[i] > nums[stack[-1]]:                dp[i] = max(dp[i] + 1, dp[stack.pop()])                res = max(res, dp[i])            stack.append(i)                    return res

捕获雨水

给出n 非负整数,代表一个高程图,其中每条的宽度为1 ,计算雨后能截留多少水。

Input: height = [0,1,0,2,1,0,1,3,2,1,2,1]Output: 6Explanation: The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped.

解决方案

class Solution(object):    def trap(self, height):        """        :type height: List[int]        :rtype: int        """        stack = []        total = 0                for i in range(len(height)):            while len(stack) > 0 and height[stack[-1]] < height[i]:                poppedIdx = stack.pop()                                if len(stack) == 0:                    break                                    heightVal = min(height[stack[-1]], height[i]) - height[poppedIdx]                length = i - stack[-1] - 1                total += heightVal * length                        stack.append(i)                return total

直方图中最大的矩形

给出一个整数数组heights ,代表直方图的条形高度,每个条形的宽度为1 ,返回直方图中最大的矩形的面积

Input: heights = [2,1,5,6,2,3]Output: 10Explanation: The above is a histogram where width of each bar is 1.The largest rectangle is shown in the red area, which has an area = 10 units.

解决方案

class Solution(object):    def largestRectangleArea(self, heights):        """        :type heights: List[int]        :rtype: int        """        maxArea = 0        stack = []        start = 0                for i, h in enumerate(heights):            start = i                        while stack and stack[-1][1] > h:                index, height = stack.pop()                maxArea = max(maxArea, height * (i - index))                start = index                        stack.append((start, h))                for i, h in stack:            maxArea = max(maxArea, h * (len(heights) -i))                    return maxArea

删除重复的字母

给定一个字符串s ,去除重复的字母,使每个字母都出现一次,而且只有一次。你必须确保你的结果在所有可能的结果中是最小的

Input: s = "bcabc"Output: "abc"

解决办法

class Solution(object):    def removeDuplicateLetters(self, s):        """        :type s: str        :rtype: str        """        d = {char: indx for indx, char in enumerate(s)}                res = []                for indx, char in enumerate(s):            if char not in res:                                while res and indx < d[res[-1]] and char < res[-1]:                    res.pop()                                    res.append(char)                return "".join(res)

删除K位数

给出代表一个非负整数的字符串numnum ,和一个整数k ,返回 k 删除 数字 后的最小的整数 num

Input: num = "1432219", k = 3Output: "1219"Explanation: Remove the three digits 4, 3, and 2 to form the new number 1219 which is the smallest.

解决方法

class Solution(object):    def removeKdigits(self, num, k):        """        :type num: str        :type k: int        :rtype: str        """        num=list(num)        stack,i=[num[0]],1                while i<len(num):            while stack and k>0 and num[i]<stack[-1]:                stack.pop()                k-=1                            stack.append(num[i])            i+=1                    while k>0:            stack.pop()            k-=1        stack="".join(stack)        return stack.lstrip("0") if stack!="" and int(stack)!=0 else "0"

132模式

给出一个由n 整数组成的数组nums132模式是由三个整数组成的子序列nums[i],nums[j]nums[k] ,使得i < j < knums[i] < nums[k] < nums[j].

如果 true ,如果有一个 132模式 nums ,否则,返回false

Input: nums = [1,2,3,4]Output: falseExplanation: There is no 132 pattern in the sequence.

解决方案

class Solution(object):    def find132pattern(self, nums):        """        :type nums: List[int]        :rtype: bool        """        if len(nums) < 3:            return False                right = float("-inf")        stack = []                for i in range(len(nums)-1, -1, -1):            if nums[i] < right:                return True            else:                while stack and stack[-1] < nums[i]:                    right = stack.pop()                        stack.append(nums[i])                return False

每日温度

给定一个整数数组temperatures ,代表每天的温度,返回一个数组 answer ,这样 answer[i]是你ith 之后要等待的天数,以获得更温暖的温度。如果未来没有可能的一天,则保留answer[i] == 0 ,而不是。

Input: temperatures = [73,74,75,71,69,72,76,73]Output: [1,1,4,2,1,1,0,0]

解决方案

class Solution(object):    def dailyTemperatures(self, temperatures):        """        :type temperatures: List[int]        :rtype: List[int]        """        # Monotonic Stack        n = len(temperatures)        stack = []        result = [0] * n                for i in range(n-1, -1, -1):            while stack and temperatures[i] >= stack[-1][0]:                stack.pop(-1)                        if len(stack) == 0:                result[i] = 0            else:                result[i] = stack[-1][1] - i                        stack.append((temperatures[i], i))                    return result

在线股票跨度

设计一个算法,收集一些股票的每日报价,并返回该股票在当日的价格跨度

该股票今天的价格跨度被定义为股票价格小于或等于今天价格的最大连续天数(从今天开始往后推)。

  • 例如,如果一只股票在接下来的7 天内的价格是[100,80,60,70,60,75,85] ,那么股票的跨度就是[1,1,1,2,1,4,6]

实现StockSpanner 类。

  • StockSpanner() 初始化该类的对象。
  • int next(int price) 鉴于今天的价格是 ,返回股票价格的price跨度
Input["StockSpanner", "next", "next", "next", "next", "next", "next", "next"][[], [100], [80], [60], [70], [60], [75], [85]]Output[null, 1, 1, 1, 2, 1, 4, 6]

解决方案

class StockSpanner(object):

子数组最小值之和

给定一个整数数组arr,求min(b) 的总和,其中b 的范围是arr 的每一个(连续的)子数组。由于答案可能很大,所以返回答案modulo 109 + 7

Input: arr = [3,1,2,4]Output: 17Explanation: Subarrays are [3], [1], [2], [4], [3,1], [1,2], [2,4], [3,1,2], [1,2,4], [3,1,2,4]. Minimums are 3, 1, 2, 4, 1, 1, 2, 1, 1, 1.Sum is 17.

解决方案

class Solution(object):    def sumSubarrayMins(self, arr):        """        :type arr: List[int]        :rtype: int        """        su = 0        n = len(arr)        arr.append(0)        stack = [-1]                for i, num in enumerate(arr):            while stack and arr[stack[-1]] > num: # (i)                idx = stack.pop()                su += arr[idx] * (i - idx) * (idx - stack[-1])                            stack.append(i)                    return su % (10**9 + 7)

最小的连续字符数

给定一个字符串s ,返回s 的词典上最小的子序列 ,该子 序列 正好 包含了 s 的所有不同字符

Input: s = "bcabc"Output: "abc"

解决方法

class Solution(object):    def smallestSubsequence(self, s):        """        :type s: str        :rtype: str        """        stack = []        d = {}                for i in range(len(s)):            d[s[i]] = i                    for i in range(len(s)):            if s[i] not in stack:                while stack and i < d[stack[-1]] and ord(s[i]) < ord(stack[-1]):                    stack.pop()                            stack.append(s[i])            return (''.join(stack))

商店中的最终价格与特别折扣

给出数组prices ,其中prices[i] 是商店中ith 的价格。如果你购买了ith ,那么你将得到相当于prices[j] 的折扣,其中j最小的索引,这样j > iprices[j] <= prices[i] ,否则,你将不会得到任何折扣。

返回一个数组,其中的 *ith*元素是你将为该商店的 *ith*的最终价格。

Input: prices = [8,4,6,2,3]Output: [4,2,4,2,3]Explanation: For item 0 with price[0]=8 you will receive a discount equivalent to prices[1]=4, therefore, the final price you will pay is 8 - 4 = 4. For item 1 with price[1]=4 you will receive a discount equivalent to prices[3]=2, therefore, the final price you will pay is 4 - 2 = 2. For item 2 with price[2]=6 you will receive a discount equivalent to prices[3]=2, therefore, the final price you will pay is 6 - 2 = 4. For items 3 and 4 you will not receive any discount at all.

解决方案

class Solution(object):    def finalPrices(self, prices):        """        :type prices: List[int]        :rtype: List[int]        """        stack = []        l = len(prices)                for i in range(0, l):            while stack and prices[stack[-1]] >= prices[i]:                p = stack.pop()                prices[p] -= prices[i]                            stack.append(i)                return prices

使数组不递减的步骤

给你一个以0为索引的整数数组nums 。在一个步骤中,移除所有元素nums[i] ,其中nums[i - 1] > nums[i] 为所有0 < i < nums.length

返回所执行 的步骤数,直到nums 成为一个 非递减 数组

Input: nums = [5,3,4,4,7,3,6,11,8,5,11]Output: 3Explanation: The following are the steps performed:- Step 1: [5,3,4,4,7,3,6,11,8,5,11] becomes [5,4,4,7,6,11,11]- Step 2: [5,4,4,7,6,11,11] becomes [5,4,7,11,11]- Step 3: [5,4,7,11,11] becomes [5,7,11,11][5,7,11,11] is a non-decreasing array. Therefore, we return 3.

解决方案

class Solution(object):    def totalSteps(self, nums):        """        :type nums: List[int]        :rtype: int        """        n = len(nums)        dp = [0] * n        res = 0        stack = []            for i in range(n-1, -1, -1):            while stack and nums[i] > nums[stack[-1]]:                dp[i] = max(dp[i] + 1, dp[stack.pop()])                res = max(res, dp[i])            stack.append(i)                    return res

最大块数的排序

给你一个长度为n 的整数数组arr ,该数组表示范围内的整数的permutation[0, n - 1]

我们把arr 分成一些小(即分区),并对每个小块进行单独排序。将它们连接起来后,其结果应该等于排序后的数组。

返回我们可以对数组进行排序的最大块数

Input: arr = [4,3,2,1,0]Output: 1Explanation:Splitting into two or more chunks will not return the required result.For example, splitting into [4, 3], [2, 1, 0] will result in [3, 4, 0, 1, 2], which isn't sorted.

解决方案

class Solution(object):    def maxChunksToSorted(self, arr):        """        :type arr: List[int]        :rtype: int        """        stack=[]        stack.append(arr[0])        top=1        largest=0                for i in range(1,len(arr)): #scan left to right from 1 to l-1            if(arr[i]>stack[top-1]): # if element on top of stack < arr[i] push element in stack and increment top of stack as well                 stack.append(arr[i])                top+=1

时间复杂度

基于单调堆栈的解决方案的时间复杂度是O(n) ,因为while循环只是将堆栈中的元素一个一个地弹出来,而且堆栈中不可能有超过n个元素被推送,因为每个元素都被推送一次。因此嵌套的while循环也不会执行超过n次。内循环在覆盖n个元素之前不会被算作一个嵌套循环。

总结

单调堆栈是堆栈的一种特殊形式,里面的所有项目都是按升序或降序排序的