栈和队列都是为了保证元素的一种顺序性。(其实递归也是有顺序性的(运行到最后一步,再往前走),所以可用stack来模拟递归过程)
单调队列:队列的性质是先进先出,为什么滑动窗口的最大值这个题用单调递减队列?因为窗口往右边滑动的时候,队列前端的值不在窗口里了,把队列前端的值弹出。右侧的大值,一定可以屏蔽左侧的小值。
为什么862用单调递增队列?因为根据前缀和性质,用队列尾减去队列前,得到的就是这中间值之和,本质上是比较窗口的两端元素。
单调栈:栈的性质是先后进先出,为什么接雨水这个题用单调递减栈?因为需要比较的是相邻(不一定是挨着)的元素,而不是比较窗口两端元素。

规律:求s[i]的左边界和右边界。

目录
42 接雨水
接雨水和柱形图最大面积,一个是凹槽,一个是凸起,凹槽是找左边的最大值和右边的最大值,所以不需要哨兵节点。凸起找左边的最小值和右边的最小值,需要哨兵节点。
接雨水问题(python实现单调栈问题)_meng_xin_true的博客-CSDN博客

class Solution(object):
def trap(self, height):
"""
:type height: List[int]
:rtype: int
"""
#维护一个单调递减栈
ans=0
stack=[]
for i in range(len(height)):
while len(stack) and height[i]>height[stack[-1]]:
cur_index=stack.pop()
if len(stack)==0:
break
left = height[stack[-1]]
right = height[i]
distans = i - stack[-1] -1
cur_ans= (min(left,right)-height[cur_index])*distans
ans=ans+cur_ans
stack.append(i)
return ans
class Solution:
def trap(self, height: List[int]) -> int:
#单调递减栈。栈顶存储最小元素。
#栈顶元素是凹槽,栈顶前一个元素是左边挡板,当前元素(如果比栈顶大),是右边挡板
ans=0
stack=[]
for i in range(len(height)):
#如果当前元素大于栈顶,当前元素是右挡板
right=height[i]
while stack and right>height[stack[-1]]:
mid_index=stack.pop() #凹槽
if not stack:
break
left=height[stack[-1]] #倒数第二个元素是左挡板
h=min(left,right)-height[mid_index]
w=i-stack[-1]-1
cur_ans=h*w
ans+=cur_ans
stack.append(i)
return ans
84 柱状图的最大矩形
接雨水和柱形图最大面积,一个是凹槽,一个是凸起,凹槽是找左边的最大值和右边的最大值,所以不需要哨兵节点。凸起找左边的最小值和右边的最小值,需要哨兵节点。
https://segmentfault.com/a/1190000022791301

单调递增栈。遍历数组,把数组下标放到stack中,如果当前元素小于stack的栈顶元素,弹出stack栈顶元素作为高度,宽度就是留守的Stack栈顶元素和当前元素下表的差值。
注意哨兵节点的使用。
class Solution(object):
def largestRectangleArea(self, heights):
"""
:type heights: List[int]
:rtype: int
"""
size = len(heights)
# 这里我们使用哨兵节点,这样就能够避免非空判断
heights = [0] + heights+[0]
# 数组长度发生了变化
size += 2
# 将哨兵先放入栈中
stack = [0]
ans = 0
for i in range(1, size):
# 与栈顶元素比较,进行判断是否要入栈
# 出栈的逻辑
while heights[i] < heights[stack[-1]]:
cur_height = heights[stack.pop()]
# 确定宽度
cur_width = i - stack[-1] - 1
ans = max(ans, cur_height * cur_width)
stack.append(i)
return ans
class Solution(object):
def largestRectangleArea(self, heights):
"""
:type heights: List[int]
:rtype: int
"""
ans=0
heights=[0]+heights+[0]
stack=[]
for i in range(len(heights)):
while len(stack) and heights[i]<heights[stack[-1]]:
cur_index=stack.pop()
if len(stack)==0:
break
cur_height=heights[cur_index]
distance=i-stack[-1]-1
cur_ans=cur_height*distance
ans=max(ans,cur_ans)
stack.append(i)
return ans
85 最大矩形


遍历每行,以每行为底边,可以看作是求最大柱状图的面积。
当这行中,元素为0的时候,柱状图列表为0,元素为1的时候,直接和上一行的进行累加。
class Solution(object):
def maxarea(self,height):
stack=[]
height=[0]+height+[0]
ans=0
for i in range(len(height)):
while len(stack) and height[i]<height[stack[-1]]:
cur_index=stack.pop()
cur_height=height[cur_index]
distnce=i-stack[-1]-1
cur_ans=cur_height*distnce
ans=max(ans,cur_ans)
stack.append(i)
return ans
def maximalRectangle(self, matrix):
"""
:type matrix: List[List[str]]
:rtype: int
"""
if len(matrix)==0:
return 0
res=0
height=[0 for _ in range(len(matrix[0]))]
for i in range(len(matrix)):
cur=matrix[i]
for j in range(len(cur)):
if cur[j]=="1":
height[j]=height[j]+1
elif cur[j]=="0":
height[j]=0
print(height)
cur_ans=self.maxarea(height)
res=max(res,cur_ans)
return res
221 最大正方形

最大矩形加一行代码。在计算面积的时候限制一下distance和height的大小。
class Solution(object):
def maxarea(self,height):
stack=[]
height=[0]+height+[0]
ans=0
for i in range(len(height)):
while len(stack) and height[i]<height[stack[-1]]:
cur_index=stack.pop()
cur_height=height[cur_index]
distnce=i-stack[-1]-1
if distnce>=cur_height:
cur_ans=cur_height*cur_height
ans=max(ans,cur_ans)
stack.append(i)
return ans
def maximalSquare(self, matrix):
"""
:type matrix: List[List[str]]
:rtype: int
"""
if len(matrix)==0:
return 0
res=0
height=[0 for _ in range(len(matrix[0]))]
for i in range(len(matrix)):
cur=matrix[i]
for j in range(len(cur)):
if cur[j]=="1":
height[j]=height[j]+1
elif cur[j]=="0":
height[j]=0
print(height)
cur_ans=self.maxarea(height)
res=max(res,cur_ans)
return res
239 滑动窗口最大值

暴力解法,时间复杂度o(nk).超时。

class Solution(object):
def maxSlidingWindow(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
res=[]
for i in range(len(nums)-k+1):
left=i
right=i+k
cur_max=nums[left]
for j in range(left+1,right):
cur_max=max(cur_max,nums[j])
res.append(cur_max)
return res
单调队列
空间换时间,时间复杂度o(n)



import collections
#单调递减队列
class Solution:
def maxSlidingWindow(self, nums, k):
if not nums or k == 0: return []
deque = collections.deque()
for i in range(k):
#前一个数字,小于当前数字。把前一个数字弹出
while deque and deque[-1] < nums[i]:
deque.pop()
deque.append(nums[i])
print(deque)
res = [deque[0]]
#i表示滑动窗口最右边
for i in range(k, len(nums)):
#如果队列头部值,是上一个窗口的值,就弹出去。
if deque[0] == nums[i - k]:
deque.popleft()
while deque and deque[-1] < nums[i]:
deque.pop()
deque.append(nums[i])
res.append(deque[0])
print(i,res,deque)
return res
nums=[2,3,4,2,6,2,5,1]
k=3
result=Solution().maxSlidingWindow(nums,k)
print(result)
739. 每日温度

class Solution:
def dailyTemperatures(self, T: List[int]) -> List[int]:
stack = []
res = [0] * len(T)
for i in range(len(T)):
while stack and T[stack[-1]] < T[i]:
res[stack[-1]] = i - stack[-1]
stack.pop()
stack.append(i)
return res
作者:xiao-xue-66
链接:https://leetcode-cn.com/problems/daily-temperatures/solution/pythonti-jie-dan-diao-di-jian-zhan-by-xiao-xue-66/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution(object):
def dailyTemperatures(self, T):
"""
:type T: List[int]
:rtype: List[int]
"""
#单调递减栈
stack=[]
res=[0 for _ in range(len(T))]
for i in range(len(T)):
while len(stack) and T[stack[-1]]<T[i]:
cur_index=stack.pop()
cur_res=i-cur_index
res[cur_index]=cur_res
stack.append(i)
return res
5614. 找出最具竞争力的子序列

https://leetcode-cn.com/problems/find-the-most-competitive-subsequence/solution/python3-shuang-bai-jie-fa-by-ni-jiao-wo-dpma/
class Solution(object):
def mostCompetitive(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
#维护一个单调递增栈
stack=[]
n=len(nums)
for i in range(n):
while len(stack) and stack[-1]>nums[i] and (n-i-1)>= k-len(stack):
stack.pop()
stack.append(nums[i])
return stack[:k]
678. 有效的括号字符串

题解:开始的是我是用的,在一开始,我只是单纯的记录左右括号数量的差值和星的个数。这样做忽略了第三个条件。
要在栈里记录位置信息。
先看一下我最开始的题解
class Solution(object):
def checkValidString(self, s):
"""
:type s: str
:rtype: bool
"""
stack=[]
cnt=0
for i in range(len(s)):
cur=s[i]
if cur=='(':
stack.append(cur)
elif cur==')':
if len(stack)==0:
#用星号模拟左括号
if cnt>0:
cnt=cnt-1
else:
stack.pop()
elif cur=="*":
cnt=cnt+1
if len(stack)==0:
return True
#用星号模拟右括号
if len(stack)==cnt:
return True
return False
class Solution(object):
def checkValidString(self, s):
"""
:type s: str
:rtype: bool
"""
stack=[]
stack_star=[]
cnt=0
for i in range(len(s)):
cur=s[i]
if cur=='(':
stack.append(i)
elif cur==')':
if len(stack)==0:
#用星号模拟左括号
if len(stack_star)>0:
stack_star.pop()
else:
return False
else:
stack.pop()
elif cur=="*":
stack_star.append(i)
if len(stack)==0:
return True
#stack不为空的时候
while stack:
if not stack_star:
return False
elif stack[-1]>stack_star[-1]:
return False
else:
stack.pop()
stack_star.pop()
return True
32. 最长有效括号

//最长的有效括号--------------->括号子串,括号子串的结尾一定是')',根据右括号为右端点去寻找合法的最长子串对应的左括号
//只包含'('和')'
public int longestValidParentheses(String s) {
int n = s.length(), maxLen = 0;
Deque<Integer> stack = new ArrayDeque<>();
stack.push(-1);
for (int i = 0; i < n; i++) {
char ch = s.charAt(i);
// 如果是左括号直接入栈即可
if (ch == '(')
stack.push(i);
else {
// 如果是右括号,存在两种情况
// 1.如果前面可以有左括号和它进行匹配,那么就存在一个由左括号、右括号组成的子串
// 2.如果前面没有左括号和它进行匹配,那么这个右括号就形成了新的边界。新的子串匹配时,起点必须在该边界右边
if (stack.size() == 1) {
// 如果栈的大小为1,说明只存放了边界
// 说明存放的内容为:边界
// 相应的做法为边界替换,将旧的边界出栈,然后放入新的边界
// 说明该右括号找不到和它可以匹配的左括号,那么该子串长度无效。同时这个右括号形成了新的边界(参照物)
stack.pop();
stack.push(i);
} else {
// 如果栈的大小不为1
// 说明存放的内容为:边界+左括号索引+左括号索引+左括号索引......
// 说明可以找到和它进行匹配的左括号
stack.pop();
maxLen = Math.max(maxLen,i-stack.peek());
}
}
}
return maxLen;
}
2
踩
查看 1 条回复
回复
907. 子数组的最小值之和

单调栈+组合数学
496. 下一个更大元素 I

单调递减栈
class Solution(object):
def nextGreaterElement(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: List[int]
"""
#找右的下一个更大元素,从后往前遍历,维护一个单调递减栈
#为什么要从后往前遍历,因为是要找右侧,只有从后往前遍历才能比较到当前元素和右侧元素
#先用单调递减栈找到nums2的下一个更大元素
stack=[]
next_max=[-1 for _ in range(len(nums2))]
for i in range(len(nums2)-1,-1,-1):
#判断当前元素和右侧元素(右侧元素存储在单调栈中,栈顶是挨着最近的)
while stack and nums2[i]>stack[-1]:
stack.pop()
if stack and nums2[i]<stack[-1]:
next_max[i]=stack[-1]
stack.append(nums2[i])
res=[]
for i in range(len(nums1)):
num=nums1[i]
num_index=nums2.index(num)
cur_ans=next_max[num_index]
res.append(cur_ans)
return res
503. 下一个更大元素 II

环形数组+单调递减栈
class Solution(object):
def nextGreaterElements(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
#用取模来模拟环形数组
stack=[]
n=len(nums)
res=[-1 for _ in range(len(nums))]
for i in range(n*2-1,-1,-1):
#如果当前元素大于栈顶元素
while stack and nums[i%n]>=stack[-1]:
stack.pop()
if stack and nums[i%n]<stack[-1]:
res[i%n]=stack[-1]
stack.append(nums[i%n])
return res
2104. 子数组范围和

这个题是找的下一个更大/更小元素,和最大宽度坡不一样,最大宽度坡找的不是下一个更大/更小元素。

计算 右侧比5小的第一个数,从右往左遍历,单调递增栈。
入栈:[2],1比2小,2出栈,1入栈[1],4入栈,[1,4],7入栈,[1,4,7],6比7小,7出栈,6入栈,[1,4,6]
为什么是单调递增栈?因为要求的是右边第一个比cur小的数,假设右侧j小于i,i出栈,j入栈,假如i比cur小,那么j也一定比cur小,j更靠近cur,所以可用小数屏蔽大数。

class Solution:
def subArrayRanges(self, nums: List[int]) -> int:
# nums[i]是多少子数组的最大值,以及多少子数组的最小值。相加
# 求nums[i]是多少子数组的最大值,找到nums[i]左边和右边的边界,2个单调栈
# 求nums[i]是多少子数组的最小值,找到nums[i]左边和右边的边界,2个单调栈。共4个单调栈
left_max = [-1 for i in range(len(nums))]
right_max = [-1 for i in range(len(nums))]
left_min = [-1 for i in range(len(nums))]
right_min = [-1 for i in range(len(nums))]
# 最大值,往右边可以辐射到哪里,单调递减栈
stack = []
for i in range(len(nums) - 1, -1, -1):
while stack and nums[i] > nums[stack[-1]]:
stack.pop()
#当前元素比stack[-1]小,说明从stack[-1]开始已经不是最大值了,右边的边界就是stack[-1]
if stack and nums[i] <= nums[stack[-1]]:
right_max[i] = stack[-1]
else:
right_max[i] = len(nums)
stack.append(i)
# 最大值,往左边可以辐射到哪里,单调递减栈
stack = []
for i in range(len(nums)):
while stack and nums[i] >= nums[stack[-1]]:
stack.pop()
# 当前元素比stack[-1]小,说明从stack[-1]开始已经不是最大值了,右边的边界就是stack[-1]
if stack and nums[i] < nums[stack[-1]]:
left_max[i] = stack[-1]
else:
left_max[i] = -1
stack.append(i)
# 最小值,往右边可以辐射到哪里,单调递递增
stack = []
for i in range(len(nums) - 1, -1, -1):
while stack and nums[i] < nums[stack[-1]]:
stack.pop()
# 当前元素比stack[-1]大,说明从stack[-1]开始已经不是最小值了,右边的边界就是stack[-1]
if stack and nums[i] >= nums[stack[-1]]:
right_min[i] = stack[-1]
else:
right_min[i] = len(nums)
stack.append(i)
# 最小值,往左边边可以辐射到哪里,单调递增
stack = []
for i in range(len(nums)):
while stack and nums[i] <= nums[stack[-1]]:
stack.pop()
# 当前元素比stack[-1]大,说明从stack[-1]开始已经不是最小值了,左边的边界就是stack[-1]
if stack and nums[i] > nums[stack[-1]]:
left_min[i] = stack[-1]
else:
left_min[i] = -1
stack.append(i)
print(left_max)
print(right_max)
print(left_min)
print(right_min)
ans = 0
for i in range(len(nums)):
ans += nums[i] * (i - left_max[i]) * (right_max[i] - i)
ans -= nums[i] * (i - left_min[i]) * (right_min[i] - i)
return ans
962. 最大宽度坡

贪心+单调栈
左边界尽可能靠左,右边界尽可能靠右。
左边界尽可能靠左,所以单调递减栈从0下标开始。
右边界尽可能靠右,所以从右往左遍历



class Solution:
def maxWidthRamp(self, A: List[int]) -> int:
stack = []
n = len(A)
for i in range(n):
if not stack or A[stack[-1]] > A[i]:
stack.append(i)
res = 0
i = n - 1
while i > res: # 当res大于等于i时没必要继续遍历了
while stack and A[stack[-1]] <= A[i]:
res = max(res, i - stack[-1])
stack.pop()
i -= 1
return res
作者:Elmer
链接:https://leetcode-cn.com/problems/maximum-width-ramp/solution/dan-diao-zhan-python-yi-kan-jiu-dong-by-elmer/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1124. 表现良好的最长时间段

class Solution:
def longestWPI(self, hours: List[int]) -> int:
n = len(hours)
score = [1 if h >8 else -1 for h in hours]
presum = [0] * (n + 1)
for i in range(1, n+1):
presum[i] = presum[i-1] + score[i-1]
stack = []
for i in range(n+1):
if not stack or presum[stack[-1]] > presum[i]:
stack.append(i)
ans = 0
i = n
while i > ans:
while stack and presum[stack[-1]] < presum[i]:
ans = max(ans, i - stack.pop())
i -= 1
return ans
作者:shzi
链接:https://leetcode-cn.com/problems/longest-well-performing-interval/solution/dan-diao-zhan-by-shzi/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution:
def longestWPI(self, hours: List[int]) -> int:
#前缀和求出来当前i以及以前,劳累的天数是不是严格大于不劳累的天数
#如果不把presum的长度+1,那么要计算j到i之间的天数,应该是presum[i]-presum[j-1],不好处理边界
#为什么是presum[i]-presum[j-1],而不是presum[i]-presum[j],因为要求的区间包括j,要从j的前一位开始减。
n = len(hours)
score = [1 if h >8 else -1 for h in hours]
presum = [0] * (n + 1)
for i in range(1, n+1):
presum[i] = presum[i-1] + score[i-1]
print(presum)
#presum[i] - presum[j] > 0,表示「劳累的天数」是严格 大于「不劳累的天数」
#j小于i且presum[j]小于presum[i],变成了962
#单调递减栈
stack=[]
for i in range(len(presum)):
if not stack or presum[i] < presum[stack[-1]]:
stack.append(i)
print(stack)
res=0
for i in range(len(presum)-1,-1,-1):
while stack and presum[i] > presum[stack[-1]]:
res=max(res,i-stack[-1])
stack.pop()
return res
2334. 元素值大于变化阈值的子数组


计算器
224. 基本计算器

双栈
class Solution(object):
def calculate(self, s):
"""
:type s: str
:rtype: int
"""
#去掉空格
s = s.replace(" ","")
from collections import deque
# 运算符号优先级
hashmap = {'-': 1,
'+': 1,
'*': 2,
'/': 2,
'%': 2,
'^': 3,
}
# 存放所有数字
nums = deque([0]) # 为了防止第一个数为负数,先往nums加个0
# 存放所有的操作
ops = deque([])
def calc(nums, ops):
if not nums or len(nums) < 2 or not ops:
return
b, a = nums.pop(), nums.pop()
op = ops.pop()
ans = 0
if op == '+':
ans = a + b
elif op == '-':
ans = a - b
elif op == '*':
ans = a * b
elif op == '/':
ans = a // b
elif op == '^':
ans = int(pow(a, b))
elif op == '%':
ans = a % b
nums.append(ans)
n = len(s)
i = 0
while i < n:
c = s[i]
#左括号入栈ops
if c == '(':
ops.append(c)
#右括号计算
elif c == ')':
while ops:
if ops[-1] != '(':
calc(nums, ops)
else:
ops.pop()
break
else:
#如果是数字,入栈nums
if c.isdigit():
tmp = c
j = i + 1
#比如几位连在一起的,“150”
while j < n and s[j].isdigit():
tmp += s[j]
j += 1
nums.append(int(tmp))
i = j - 1
else:
#处理特殊case,如果当前是,加号或者减号,但是前面不是数字的时候
if i > 0 and s[i - 1] in '(+-':
nums.append(0)
#如果是 加号,或者减号
while ops and ops[-1] != '(':
#前一位如果小于等于当前的运算优先级,就计算出来。
#例子:s="2-1+2"
prev = ops[-1]
if hashmap[prev] >= hashmap[c]:
calc(nums, ops)
else:
break
ops.append(c)
i += 1
while ops and ops[-1] != '(':
calc(nums, ops)
return nums[-1]
6202. 使用机器人打印字典序最小的字符串
栈


class Solution:
def robotWithString(self, s: str) -> str:
ans = []
cnt = Counter(s)
min = 0 # 剩余最小字母
st = []
for c in s:
cnt[c] -= 1
while min < 25 and cnt[ascii_lowercase[min]] == 0:
min += 1
st.append(c)
while st and st[-1] <= ascii_lowercase[min]:
ans.append(st.pop())
return ''.join(ans)
作者:endlesscheng
链接:https://leetcode.cn/problems/using-a-robot-to-print-the-lexicographically-smallest-string/solution/tan-xin-zhan-by-endlesscheng-ldds/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
https://programmercarl.com/0042.%E6%8E%A5%E9%9B%A8%E6%B0%B4.html#%E5%8D%95%E8%B0%83%E6%A0%88%E8%A7%A3%E6%B3%95

312

被折叠的 条评论
为什么被折叠?



