栈与队列
1.用栈实现队列
2.用队列实现栈
3.有效的括号
def isValid(self, s: str) -> bool:
stack = []
for i in s:
if i == "[":
stack.append("]")
elif i == "{":
stack.append("}")
elif i == "(":
stack.append(")")
elif not stack or stack[-1] != i:
return False
else:
stack.pop()
return not stack # 等价于len(stack) == 0 or stack is None
4.删除字符串中的所有相邻重复项
so easy
def removeDuplicates(self, s: str) -> str:
stack = []
for i in s:
if stack and stack[-1] == i:
stack.pop()
else:
stack.append(i)
return ''.join(stack)
5.逆波兰表达式求值(2)
so easy
def evalRPN(self, tokens: List[str]) -> int:
stack = []
ch = {
'+':lambda a,b:a+b,
'-':lambda a,b:a-b,
'*':lambda a,b:a*b,
'/':lambda a,b:int(a/b),
}
for s in tokens:
if s not in ch.keys():
stack.append(int(s))
else:
b = stack.pop()
a = stack.pop()
r = ch[s](a,b)
stack.append(r)
return stack.pop()
6.滑动窗口最大值(3)
单调队列核心思想:之前的元素比当前元素还小的话,可以提前移除之前的元素,不需要再维护无用的元素
注意清除已过期的元素
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
# from collections import deque 导入
q = deque()
# 将前k个元素入队
for i in range(k):
while q and nums[i] >= nums[q[-1]]:
q.pop()
q.append(i)
res = [nums[q[0]]]
for i in range(k,n):
while q and nums[i] >= nums[q[-1]]:
q.pop()
q.append(i)
if i - q[0] >= k:
q.popleft()
res.append(nums[q[0]])
return res
精简融合版
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
# 队列中存的是元素下标
q = deque()
res = []
for i in range(n):
# 队列不为空 且 当前元素大于队列中元素时,移除队列中的元素
while q and nums[i] >= nums[q[-1]]:
q.pop()
# 添加到队列中
q.append(i)
# 滑动窗口大小大于k,移除队头元素(一定要在下一步之前)
# q[0]和q[1]都在队列中,q[0]一定比q[1]早过期,
# 若q[1]比q[0]大,q[1]入队时,q[0]便会出队
# 若q[1]比q[0]小,q[1]肯定是后入队的,否则q[0]会把q[1]排除掉
if i - q[0] >= k:
q.popleft()
# 滑动窗口大小为k时,添加结果
if i + 1 >= k:
res.append(nums[q[0]])
return res
7.前 K 个高频元素(2)
给你一个整数数组
nums
和一个整数k
,请你返回其中出现频率前k
高的元素。你可以按 任意顺序 返回答案。
最大堆:有n个元素,每次维护要logk的复杂度,所以总时间复杂度为nlogk,空间复杂度最差map要存储n个键值对,为O(n)
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
mp = {}
for n in nums:
mp[n] = mp.get(n,0) + 1
q = []
for key,value in mp.items():
heapq.heappush(q,(value,key))
if len(q) > k:
heapq.heappop(q)
res = [0] * k
for i in range(k - 1,-1,-1):
res[i] = heapq.heappop(q)[1]
return res
单调队列
单调栈
在一维数组中对每一个数找到第一个比自己小的元素。这类“在一维数组中找第一个满足某种条件的数”的场景就是典型的单调栈应用场景。The Next Greater问题
1.柱状图中最大的矩形(3)
基本思路:遍历每个矩阵,以这个矩阵为中心(即高度是此矩阵)向左右扩展,找比这个矩阵最近小的元素。
暴力法:O(n^2),leetcode 测试案例87/98
def largestRectangleArea(self, heights: List[int]) -> int:
# 从左向右遍历:以每一根柱子为主心骨(当前轮最高的参照物),迭代直到找到左侧和右侧各第一个矮一级的柱子
res = 0
for i in range(len(heights)):
left = i
right = i
for _ in range(left,-1,-1):
if heights[left] < heights[i]:
break
left -= 1
for _ in range(right,len(heights)):
if heights[right] < heights[i]:
break
right += 1
res = max(res,(right-left-1) * heights[i])
return res
动态规划
用两个dp数组保存左右两边最近比当前小的柱子的下标
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
# 两个DP数列储存的均是下标index
res = 0
lIndex = [0] * n
rIndex = [0] * n
lIndex[0] = -1
for i in range(1,n):
temp = i - 1
while temp >= 0 and heights[temp] >= heights[i]:
temp = lIndex[temp]
lIndex[i] = temp
rIndex[-1] = n
for i in range(n-2,-1,-1):
temp = i + 1
while temp < n and heights[temp] >= heights[i]:
temp = rIndex[temp]
rIndex[i] = temp
for i in range(n):
res = max(res,heights[i]*(rIndex[i]-lIndex[i]-1))
return res
单调栈:在此题中递增,由于单调栈的性质,中心点向右扩展就是while循环进去的时机,向左扩展就是根据当前点到栈顶索引之差
注意:当前点的扩展,可能并不是在遍历此点的周期内,可能是在遍历其他点的时候,走while循环能够从栈中弹出的时候。
tips:为防止左右越界,我们可以在左右两侧虚拟两根无限低的柱子,记高度为 0。
def largestRectangleArea(self, heights: List[int]) -> int:
q = []
heights.insert(0,0)
heights.append(0)
n = len(heights)
res = 0
for i in range(n):
while q and heights[i] < heights[q[-1]]:
cur = q.pop()
left = q[-1]
right = i - 1
res = max(res,(right - left)*heights[cur])
q.append(i)
return res
2.每日温度(2)
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
q = []
n = len(temperatures)
res = [0] * n
for i in range(n):
while q and temperatures[i] > temperatures[q[-1]]:
pp = q.pop()
res[pp] = i - pp
q.append(i)
return res
3.下一个更大元素 I
典型的单调栈问题
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
mp = {}
q = []
for i in range(len(nums2)):
while q and nums2[i] > nums2[q[-1]]:
index = q.pop()
mp[nums2[index]] = nums2[i]
q.append(i)
res = []
for num in nums1:
if num in mp:
res.append(mp[num])
else:
res.append(-1)
return res
4.下一个更大元素 II(2)
这题元素可以重复,所以往map里面放下标
def nextGreaterElements(self, nums: List[int]) -> List[int]:
mp = {}
q = []
temp = nums.copy()
for t in temp:
nums.append(t)
for i in range(len(nums)):
while q and nums[i] > nums[q[-1]]:
index = q.pop()
mp[index] = nums[i]
q.append(i)
res = []
for i in range(len(temp)):
if i in mp:
res.append(mp[i])
else:
res.append(-1)
return res
5.接雨水(3)
双指针
思路:逐个计算每个位置的雨水量,找到该位置左边和右边最高的柱子,由于宽就是1,高是min(rHeight,lHeight)-height[i],就能得出该位置的雨水量
时间复杂度为O(n^2),leetcode通过测试案例 320/322
def trap(self, height: List[int]) -> int:
sum = 0
for i in range(len(height)):
if i == 0 or i == len(height) - 1:
continue
lHeight,rHeight = height[i],height[i]
for l in range(i-1,-1,-1):
if height[l] > lHeight:
lHeight = height[l]
if lHeight == height[i]: continue
for r in range(i+1,len(height)):
if height[r] > rHeight:
rHeight = height[r]
if rHeight > lHeight:
continue
sum = sum + min(rHeight,lHeight) - height[i]
return sum
动态规划
原来双指针找左右两边的最高柱子,有冗余过程,
我们事先用两个数组保存左右两边的最高柱子的高度。
时间复杂度为O(n)
def trap(self, height: List[int]) -> int:
n = len(height)
if n <= 2:
return 0
lHeight = [0] * n
rHeight = [0] * n
lHeight[0] = height[0]
for i in range(1,n):
lHeight[i] = max(height[i],lHeight[i-1])
rHeight[-1] = height[-1]
for i in range(n-2,-1,-1):
rHeight[i] = max(height[i],rHeight[i+1])
sum = 0
for i in range(n):
sum = sum + min(lHeight[i],rHeight[i]) - height[i]
return sum
单调栈
计算雨水并不是像前两种方法一样-------逐个计算每个位置的雨水量
这里计算出
高度和前两种方法类似h = min(height[i],height[st[-1]]) - height[mid],得出左右两边最高柱子的最小值再减去底部的大小
宽度当前柱子下标减去凹陷部位前一个柱子下标再减1
最后v = h*w
def trap(self, height: List[int]) -> int:
st = [0]
sum = 0
for i in range(1,len(height)):
while st and height[i] > height[st[-1]]:
mid = st.pop()
if st:
h = min(height[i],height[st[-1]]) - height[mid]
w = i - st[-1] - 1
sum += h*w
st.append(i)
return sum