单调栈适合解决两边大小决定中间特征的问题
题目描述:
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
思路分析:这题的关键在于以某个点B为高的矩形的最大宽度为该点左边第一个小于它的点a到右边最靠近它且比其小的点c。
单调栈的特点在于:
当遇见大数的时候, 压入堆栈,等待之后处理。
当遇见小数c的时候,意味着大数b的右边界c已经确定了。
这时候开始pop, 而以被pop出来的值(b)为高度的矩形的左右边界需要被确定。
其右边界就是当前的小数。即为c。左边界是堆栈下一层的元素,因为下一层的元素一定比当前小。且是第一小的元素。这时候a也确定了。
则以被pop出来的数为高度的矩形是 (c - a - 1) * pop(), 这里pop() == b。
代码如下
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
stack = [] #记录柱状图的下标
res = 0
for i in range(n):
while len(stack)>0 and heights[i]<heights[stack[-1]]:
cur_height = heights[stack[-1]]
##注意点1
while len(stack)>0 and cur_height == heights[stack[-1]]:
stack.pop()
if len(stack)>0:
cur_width = i-stack[-1]-1#注意点2
else:
cur_width = i
res = max(res,cur_width*cur_height)
stack.append(i)
while len(stack)>0 is not None:#注意点3
cur_height = heights[stack.pop()]
while len(stack)>0 and cur_height==heights[stack[-1]]:
stack.pop()
if len(stack)>0:
cur_width = n-stack[-1]-1
else:
cur_width = n
res = max(res,cur_width*cur_height)
return res
注意点1:这里是为了判断栈尾相同的高度有几个,有几个都需要将其弹出。
注意点2:当前高度的矩形的宽度,应该是,当前点与栈尾的坐标之间的矩形个数。
注意点3:当我们遍历完数组后,剩下的数组就是单调增的(其中包括可能有连续几个高度相同的情况),处理与上面相同。
思路分析:这题解决的方法很多,例如,单调栈,动态规划,双指针,韦恩图等;
这里为了练习单调栈,我们使用此方法。
首先思考什么样的情况下能够接雨水呢,很明眼,当某一高度左边和右边都有高于其的高度时,该处就可以接雨水了。因此我们可以维护一个单调递减的栈,当遇到比栈低的高度大的时候,说明栈底处可以接雨水了。
代码如下
class Solution:
def trap(self, height: List[int]) -> int:
length=len(height)
if length<3:return 0
res,idx=0,0
stack=[]
while idx<length:
while len(stack)>0 and height[idx]>height[stack[-1]]:
top=stack.pop()#index of the last element in the stack
if len(stack)==0:
break
h=min(height[stack[-1]],height[idx])-height[top]
dist=idx-stack[-1]-1
res+=(dist*h)
stack.append(idx)
idx+=1
return res
题目描述:
根据每日气温列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0
来代替。 例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是
[1, 1, 4, 2, 1, 1, 0, 0]。 提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在
[30, 100] 范围内的整数。
思路分析:当前位置需要等待多久温度才会升高,当前温度只跟后面温度有关,我们可以维护一个栈记录温度的位置即下标,然后遍历温度列表,
1)当当前温度P大于栈底位置对应温度,那么栈底位置对应温度需要等待的时间等于当前温度坐标的减去栈底坐标,然后将栈底元素弹出,然后继续比较栈顶位置对应温度与当前温度直到p小于或等于栈底位置对应温度。
2)当当前温度p小于栈底位置对应温度,则将p的坐标压入栈。
3)最后栈中剩余的温度都是递减,即他们等待的时间都为0.
代码如下
from typing import List
class Solution:
class Solution:
def dailyTemperatures(self, T: List[int]) -> List[int]:
res = [0]*len(T)#若找不到比当前温度高的,即为0,不需要处理
stack = []
for i, v in enumerate(T):
while len(stack)>0 and v>T[stack[-1]]:
res[stack.pop()]= i-stack[-1]
stack.append(i)
return res
题目描述:
给定两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在
nums2 中的下一个比其大的值。 nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
思路分析:我们可以先不管数组nums1,先找nums2中元素右边比起大的值,并将其保存在哈希表中,然后再遍历nums1中元素,在哈希表中找到其对应的值,找不到就令其为-1.
而寻找nums2中元素右边第一个比其大的值,可以维护一个单调递减的栈,当要进栈的元素的元素比栈底的元素大的时候,那么栈顶元素右边第一个比其大的值也就找到了,这次将其弹出,并存入哈希表中。
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
stack = []
hashmap = {}
for i in range(len(nums2)):
while len(stack)>0 and nums2[i]>nums2[stack[-1]]:
hashmap[nums2[stack.pop()]]=nums2[i]
stack.append(i)
while stack:
hashmap[nums2[stack.pop()]]=-1
res = []
for i in range(len(nums1)):
res.append(hashmap[nums1[i]])
return res
题目描述
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
输入: [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
思路分析:这题与第一题不同的是,当我们利用单调栈遍历完数组的时候,还需要再从头循环找比栈底元素大的值。当我们第一遍遍历完数组得时候,单调栈中存储元素是单调递减的,所以我们再将数组一个个与栈底元素比较即可,发现比栈底元素大的值就将栈底元素弹出。这里我们只需要遍历到栈底元素对应索引的位置。此时栈中剩余元素没有比他大的值,令其为-1.
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
if not nums:return []
res = [-1]*len(nums)
stack = []
for i in range(len(nums)):
while len(stack)>0 and nums[i]>nums[stack[-1]]:
res[stack[-1]]=nums[i]
stack.pop()
stack.append(i)
for i in range(stack[-1]+1):
while len(stack)>0 and nums[i]>nums[stack[-1]]:
res[stack[-1]]=nums[i]
stack.pop()
if stack:
for p in stack:
res[p]=-1
return res
题目描述:
给你一个由 ‘(’、’)’ 和小写字母组成的字符串 s。
你需要从字符串中删除最少数目的 ‘(’ 或者 ‘)’ (可以删除任意位置的括号),使得剩下的「括号字符串」有效。
请返回任意一个合法字符串。
有效「括号字符串」应当符合以下 任意一条 要求:
空字符串或只包含小写字母的字符串
可以被写作 AB(A 连接 B)的字符串,其中 A 和 B 都是有效「括号字符串」
可以被写作 (A)的字符串,其中 A 是一个有效的「括号字符串」
基本思路:
1,利用栈的特点,从先往后扫描这个串,对于第k个括号:
若为 ( ( (,先进栈。
若为),这是判断栈是否为空:
在遍历栈,去除s中栈中记录的字符
代码如下
class Solution:
def minRemoveToMakeValid(self, s: str) -> str:
stack = []
sign = ['(',')']
for i, v in enumerate(s):
if v == '(':
stack.append(i)
elif v==')':
if not stack:stack.append(i)
else:
if s[stack[-1]]=='(':stack.pop()
else:stack.append(i)
#print(stack)
res = ""
for i, v in enumerate(s):
if i not in stack:
res += v
思路2:前一种思路,复杂度虽然是O(N),但是需要完全扫描两次字符串。
现在我们可以这样做,记录当前需要匹配的左括号的个数,从左往右扫描字符串记录当前遇到左括号的个数count,如果遇到右括号,且count不为0,那就意味着前面有左括号可以匹配,那么此时count减一,如果count为0,说明前面没有需要匹配的左括号,那么这个右括号就是无效的,我们将其删除。
遍历完以后,如果count为0,说明现在字符串是有效地,直接返回。
否则有左括号没有被匹配需要删除,注意此时我们需要从厚1往前扫描字符串删除count个左括号。
代码如下:
class Solution:
def minRemoveToMakeValid(self, s: str) -> str:
tempstr = list(s)
count = 0
for i in range(len(tempstr)):
if tempstr[i] == '(':
count+=1
elif tempstr[i] == ')':
if count == 0:
tempstr[i] = ''
else:
count-=1
for i in range(len(tempstr))[::-1]:
if count == 0:
break
if tempstr[i] == '(' and count>0:
count -=1
tempstr[i] = ''
return ''.join(tempstr)
题目描述:有效括号字符串 定义:对于每个左括号,都能找到与之对应的右括号,反之亦然。详情参见题末「有效括号字符串」部分。
嵌套深度 depth 定义:即有效括号字符串嵌套的层数,depth(A) 表示有效括号字符串 A 的嵌套深度。详情参见题末「嵌套深度」部分。
有效括号字符串类型与对应的嵌套深度计算方法如下图所示:
给你一个「有效括号字符串」 seq,请你将其分成两个不相交的有效括号字符串,A 和 B,并使这两个字符串的深度最小。
不相交:每个 seq[i] 只能分给 A 和 B 二者中的一个,不能既属于 A 也属于 B 。
A 或 B 中的元素在原字符串中可以不连续。
A.length + B.length = seq.length
深度最小:max(depth(A), depth(B)) 的可能取值最小。
划分方案用一个长度为 seq.length 的答案数组 answer 表示,编码规则如下:
answer[i] = 0,seq[i] 分给 A 。
answer[i] = 1,seq[i] 分给 B 。
如果存在多个满足要求的答案,只需返回其中任意 一个 即可。
类似地,我们可以定义任意有效括号字符串 s 的 嵌套深度 depth(S):
例如:"","()()",和 “()(()())” 都是有效括号字符串,嵌套深度分别为 0,1,2,而 “)(” 和 “(()” 都不是有效括号字符串。
基本思路:有效括号的意思就是每个左括号都可以在它右边找到与之匹配的有括号。由嵌套深度的定义可知,只有嵌套情况会增加嵌套深度,A和B连接是不会增加嵌套深度。所以给定一个有效括号字符串,其最大嵌套深度就是最长的连续的左括号数目。那么我们利用辅助栈将字符串一个个进栈,遇到左括号就进栈,遇到有括号就弹出栈。”你需要从中选出任意一组有效括号字符串 A 和 B,使 max(depth(A), depth(B)) 的可能取值最小”。这句话其实相当于让A字符串和B字符串的depth尽可能的接近。为什么呢?因为seq对应的栈上,每个左括号都对应一个深度,而这个左括号,要么是A的,要么是B的。所以,栈上的左括号只要按奇偶分配给A和B就可以啦!
代码如下:
class Solution:
def maxDepthAfterSplit(self, seq: str) -> List[int]:
#辅助栈
stack = []
#记录标记结果
res = []
count = 0 #记录括号对所在深度
for i in range(len(seq)):
if not stack or seq[i]=="(":#入栈, 记录括号对所在深度
stack.append(seq[i])
count += 1
if count%2!=0:
res.append(0)
else:
res.append(1)
else:
if count%2!=0:#出栈,此时栈顶的左括号有匹配的有括号,需要弹出
res.append(0)
else:
res.append(1)
stack.pop()
count-=1
return res
时间复杂度:O(N)