堆栈算法总结
模板例题:
给定两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
示例 1:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于num1中的数字4,你无法在第二个数组中找到下一个更大的数字,因此输出 -1。
对于num1中的数字1,第二个数组中数字1右边的下一个较大数字是 3。
对于num1中的数字2,第二个数组中没有下一个更大的数字,因此输出 -1。
示例 2:
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
stack, hashmap = list(), dict()
for i in nums2:
while len(stack) != 0 and stack[-1] < i:
hashmap[stack.pop()] = i
stack.append(i)
return [hashmap.get(i,-1) for i in nums1]
正如上面的单调栈的建立:
while stack and stack[-1]<i:
....
stack.append(i)
例题1:每日温度
leetcode 739
请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
class Solution:
def dailyTemperatures(self, T: List[int]) -> List[int]:
n = len(T)
ans = [0]*n
stack = []
for i in range(n-1,-1,-1):
while stack and T[i]>=T[stack[-1]]:
stack.pop()
#stack里面保存的永远都是从大到小的温度的index
#stack[-1]是T[i]<的最近的index
#因为是倒序遍历,所以不用考虑stack.pop()出的温度,
# 因为他的index值大于stack[-1]的index,但是温度却小于stack[-1]的温度,
# 通俗的说就是性价比较低
if stack:
ans[i] = stack[-1]-i
stack.append(i)
return ans
leetcode 901 股票价格跨度
编写一个 StockSpanner 类,它收集某些股票的每日报价,并返回该股票当日价格的跨度。
今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。
例如,如果未来7天股票的价格是 [100, 80, 60, 70, 60, 75, 85],那么股票跨度将是 [1, 1, 1, 2, 1, 4, 6]。
class StockSpanner(object):
def __init__(self):
self.stack = []
def next(self, price):
weight = 1
while self.stack and self.stack[-1][0] <= price:
weight += self.stack.pop()[1]
self.stack.append((price, weight))
return weight
例题2:
leetcode 71 简化路径
以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。
在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (…) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs 相对路径
请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。最后一个目录名(如果存在)不能以 / 结尾。此外,规范路径必须是表示绝对路径的最短字符串。
示例 1:
输入:"/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。
示例 2:
输入:"/…/"
输出:"/"
解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。
示例 3:
输入:"/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
示例 4:
输入:"/a/./b/…/…/c/"
输出:"/c"
示例 5:
输入:"/a/…/…/b/…/c//.//"
输出:"/c"
示例 6:
输入:"/a//b////c/d//././/…"
输出:"/a/b/c"
class Solution:
def simplifyPath(self, path: str) -> str:
stack = []
path = path.split('/')
for i in path:
if i == '..':
if stack:
stack.pop()
elif i and i !='.':
stack.append(i)
return '/'+'/'.join(stack)
还有一个神一般的操作:
class Solution:
def simplifyPath(self, path: str) -> str:
r = []
for s in path.split('/'):
r = {'':r, '.':r, '..':r[:-1]}.get(s, r + [s])
return '/' + '/'.join(r)
例题三:接雨水
leetcode 43
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
class Solution:
def trap(self, height: List[int]) -> int:
length = len(height)
if length < 3: return 0
#res保存最后的结果,idx是列表的序号
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
例题四:柱形图中最大的矩形
leetcode 84
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
res = 0
stack = [0]
height = [0] + heights + [0]
for i in range(1,n+2):
while stack and height[i]<height[stack[-1]]:
h = height[stack.pop()]
if not stack:
break
w = i - stack[-1] -1
res = max(res,h*w)
stack.append(i)
return res
例题五:字符串的解码
leetcode 394
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例 1:
输入:s = “3[a]2[bc]”
输出:“aaabcbc”
示例 2:
输入:s = “3[a2[c]]”
输出:“accaccacc”
示例 3:
输入:s = “2[abc]3[cd]ef”
输出:“abcabccdcdcdef”
示例 4:
输入:s = “abc3[cd]xyz”
输出:“abccdcdcdxyz”
class Solution:
def decodeString(self, s: str) -> str:
stack,res,mul = [],'',0
for i in s:
if i.isdigit():
#如果有大数的情况100等
mul = mul*10 + int(i)
elif i == '[':
#这样就省了建两个栈
#将每次‘[’前的数字和字母保存为一个单独的栈
stack.append([res,mul])
res,mul = '',0
elif i == ']':
#出栈
last_res,last_mul = stack.pop()
#‘[’前的字母加上‘[’后的数字*字母
res = last_res+last_mul*res
else:
res += i
return res
例题六:下一个更大的元素(模板例题的进阶版)
leetcode 503
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
示例 1:
输入: [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
#处理循环数组的技巧,两倍的原数组
nums2 = nums[:]
nums2.extend(nums)
stack= []
res = [-1]*len(nums2)
def helper(nums):
for idx,i in enumerate(nums):
while stack and i > nums[stack[-1]]:
res[stack.pop()] = i
stack.append(idx)
helper(nums2)
return res[:len(nums)]
堆栈算法
再讲算法之前先说一下堆与栈的区别
两个栈实现队列
# -*- coding:utf-8 -*-
class Solution:
def __init__(self):
self.stack1 = []
self.stack2 = []
def push(self, node):
# write code here
self.stack1.append(node)
def pop(self):
# return xx
if self.stack2 == []:
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
包含min函数的栈
# -*- coding:utf-8 -*-
class Solution:
def __init__(self):
self.stack = []
self.assit = []
def push(self, node):
# write code here
min_data = self.min()
if min_data > node or len(self.stack)==0:
self.stack.append(node)
self.assit.append(node)
else:
self.stack.append(node)
def pop(self):
# write code here
if self.stack:
if self.stack[-1] == self.assit[-1]:
self.stack.pop()
self.assit.pop()
else:
self.stack.pop()
def top(self):
# write code here
if self.stack>0:
return self.stack[-1]
def min(self):
# write code here
if self.assit:
return self.assit[-1]
栈的压入弹出
# -*- coding:utf-8 -*-
class Solution:
def IsPopOrder(self, pushV, popV):
# write code here
stack = []
while popV:
if stack and stack[-1] == popV[0]:
popV.pop(0)
stack.pop()
elif pushV :
stack.append(pushV.pop(0))
else:
return False
return True
滑动窗口的最大值(queue中保存的是滑动窗口最大值的下标)
# -*- coding:utf-8 -*-
class Solution:
def maxInWindows(self, num, size):
# write code here
queue,res,i = [],[],0
while size>0 and i<len(num):
if len(queue)>0 and i-size+1>queue[0]:
queue.pop(0)
while len(queue)>0 and num[queue[-1]] < num[i]:
queue.pop()
queue.append(i)
if i>=size-1:
res.append(num[queue[0]])
i +=1
return res
电话号码的字母组合:
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits:
return []
d = [" ","*","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
res = ['']
for i in digits:
letter = d[ord(i)-48]
size = len(res)
for _ in range(size):
tmp = res.pop(0)
for j in letter:
res.append(tmp+j)
return res
有效的括号:
class Solution:
def isValid(self, s: str) -> bool:
if not s:
return True
dict = {'(':')','[':']','{':'}','?':'?'}
stack = ['?']
for i in s:
if i in dict:
stack.append(i)
elif dict[stack.pop()] !=i :
return False
return len(stack)==1
接雨水:
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
简化路径
class Solution:
def simplifyPath(self, path: str) -> str:
stack = []
path = path.split('/')
for i in path:
if i == '..':
if stack:
stack.pop()
elif i and i !='.':
stack.append(i)
return '/'+'/'.join(stack)
柱形图中最大的矩形
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
res = 0
stack = [0]
height = [0] + heights + [0]
for i in range(1,n+2):
while stack and height[i]<height[stack[-1]]:
h = height[stack.pop()]
if not stack:
break
w = i - stack[-1] -1
res = max(res,h*w)
stack.append(i)
return res
leetcode 85 最大矩形
给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
输入:matrix = [[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“1”,“1”,“1”,“1”],[“1”,“0”,“0”,“1”,“0”]]
输出:6
解释:最大矩形如上图所示。
其实就是把上面的矩形的高在矩阵中构造出来,然后用上面的最大栈的方法求即可。
class Solution:
def maximalRectangle(self, matrix: List[List[str]]) -> int:
if not matrix:
return 0
row = len(matrix)
col = len(matrix[0])
height = [[0]*(col+2) for i in range(row)]
res = 0
for i in range(row):
for j in range(col):
if matrix[i][j] == '1':
height[i][j+1] = height[i-1][j+1]+1 if i>0 else 1
for i in range(row):
stack = []
for j in range(col+2):
while stack and height[i][stack[-1]]>height[i][j]:
h = height[i][stack.pop()]
if not stack:
break
w = j - 1 - stack[-1]
res = max(h*w,res)
stack.append(j)
return res
逆波兰表达式求值
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
f = ['+','-','*','/']
stack = []
res = 0
if len(tokens)==1:
return int(tokens[0])
for i in tokens:
if i not in f:
stack.append(i)
elif i == '+':
right = int(stack.pop())
left = int(stack.pop())
res = left + right
stack.append(res)
elif i == '-':
right = int(stack.pop())
left = int(stack.pop())
res = left - right
stack.append(res)
elif i == '*':
right = int(stack.pop())
left = int(stack.pop())
res = left * right
stack.append(res)
else:
right = int(stack.pop())
left = int(stack.pop())
res = left / right
stack.append(res)
return int(res)
最小栈
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack = []
def push(self, x: int) -> None:
return self.stack.append(x)
def pop(self) -> None:
return self.stack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return min(self.stack)
数组中第K个最大的元素
#最大堆
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
把最大的元素放在数组的头部
def adjust_heap(idx, max_len):
left = 2 * idx + 1
right = 2 * idx + 2
max_loc = idx
if left < max_len and nums[max_loc] < nums[left]:
max_loc = left
if right < max_len and nums[max_loc] < nums[right]:
max_loc = right
if max_loc != idx:
nums[idx], nums[max_loc] = nums[max_loc], nums[idx]
adjust_heap(max_loc, max_len)
# 建堆
n = len(nums)
for i in range(n // 2 - 1, -1, -1):
adjust_heap(i, n)
#print(nums)
res = None
for i in range(1, k + 1):
#print(nums)
res = nums[0]
nums[0], nums[-i] = nums[-i], nums[0]
adjust_heap(0, n - i)
return res
#快排
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
def partition(left, right):
pivot = nums[left]
l = left + 1
r = right
while l <= r:
if nums[l] < pivot and nums[r] > pivot:
nums[l], nums[r] = nums[r], nums[l]
if nums[l] >= pivot:
l += 1
if nums[r] <= pivot:
r -= 1
nums[r], nums[left] = nums[left], nums[r]
return r
left = 0
right = len(nums) - 1
while 1:
idx = partition(left, right)
if idx == k - 1:
return nums[idx]
if idx < k - 1:
left = idx + 1
if idx > k - 1:
right = idx - 1
基本计数器
class Solution:
def calculate(self, s: str) -> int:
stack = []
# 记录数字的符号, 因为题目说没有负数,说明第一个为正数,设为1
sign = 1
# 数字
num = 0
# 结果
res = 0
for c in s:
if c.isdigit():
num = num * 10 + int(c)
elif c == "+":
res += sign * num
# 为下一次做准备
num = 0
sign = 1
elif c == "-":
res += sign * num
# 为下一次做准备
num = 0
sign = -1
elif c == "(":
stack.append(res)
stack.append(sign)
sign = 1
res = 0
elif c == ")":
res += sign * num
num = 0
res = stack.pop() * res + stack.pop()
res += sign * num
return res
删除无效的括号
class Solution:
def removeInvalidParentheses(self, s:str) -> List[str]:
def isValid(s:str)->bool:
cnt = 0
for c in s:
if c == "(": cnt += 1
elif c == ")": cnt -= 1
if cnt < 0: return False # 只用中途cnt出现了负值,你就要终止循环,已经出现非法字符了
return cnt == 0
# BFS
level = {s} # 用set避免重复
while True:
valid = list(filter(isValid, level)) # 所有合法字符都筛选出来
if valid: return valid # 如果当前valid是非空的,说明已经有合法的产生了
# 下一层level
next_level = set()
for item in level:
for i in range(len(item)):
if item[i] in "()": # 如果item[i]这个char是个括号就删了,如果不是括号就留着
next_level.add(item[:i]+item[i+1:])
level = next_level
计算右侧小于当前元素的个数
import bisect
class Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
re_nums = nums[::-1]
bi_arr = []
res = []
for _ in re_nums:
#插入_数字时在数组中的位置
pos = bisect.bisect_left(bi_arr, _)
res.append(pos)
#插入_
bisect.insort_left(bi_arr, _)
return res[::-1]
根据身高重建队列
class Solution:
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
people.sort(key = lambda x: (-x[0], x[1]))
output = []
for p in people:
output.insert(p[1], p)
return output