栈
1. 有效的括号
- 题目描述
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串,判断字符串是否有效。有效字符串需满足:左括号必须用相同类型的右括号闭合,左括号必须以正确的顺序闭合。 - 解题思路
使用栈来匹配括号。遇到左括号时,将其压入栈中;遇到右括号时,检查栈顶元素是否为对应的左括号,如果是则弹出栈顶元素,否则返回无效。最后在判断该栈是否为空; - 代码
class Solution: def isValid(self, s: str) -> bool: n = len(s) if n == 0: return True dic = {')':'(', ']':'[', '}':'{'} sta = [] for c in s: if c not in dic: sta.append(c) else: if len(sta) != 0 and sta[-1] == dic[c]: sta.pop() else: return False return True if len(sta) == 0 else False
2. 最小栈
- 题目描述
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。实现 MinStack 类。 - 解题思路
==使用两个栈,一个栈存储所有元素,另一个栈存储每个状态下的最小元素。==每次入栈时,如果当前元素和最小栈顶元素的最小值压入最小栈;出栈时,两个栈同时出栈。 - 代码
class MinStack: def __init__(self,): self.s = [] self.min_values = [] def pop(self): self.s.pop() self.min_values.pop() def push(self, x): if len(self.s) == 0: self.s.append(x) self.min_values.append(x) else: self.s.append(x) cur_min = min(self.min_values[-1], x) self.min_values.append(cur_min) def top(self): return self.s[-1] def getMin(self): return self.min_values[-1]
3. 字符串解码
题目描述
给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。解题思路
使用栈来处理嵌套的括号和重复次数。遍历字符串,分别将字符压入到栈中,当遇到’]‘时将’[‘和’]'之间的字符弹出,然后继续弹出数字,将此部分解码后压入到栈中,然后接着遍历;代码
class Solution: def decodeString(self, s: str) -> str: # num_char = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] sta = [] for c in s: if c != ']': # 如果不是右括号,直接压入 sta.append(c) else: cur_s = [] while True: # 将左右括号之间的字符弹出 if sta[-1] != '[': cur_s.append(sta.pop()) else: sta.pop() break nums = [] # 弹出数字 while len(sta) != 0 and sta[-1] >= '0' and sta[-1] <= '9': nums.append(sta.pop()) num = int("".join(nums[::-1])) cur = "".join(cur_s[::-1]) cur = cur * num sta.append(cur) return "".join(sta)
4. 每日温度
题目描述
请根据每日气温列表,重新生成一个列表,要求其对应位置的输出是:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。解题思路
单调栈就是用来求取第一个比当前元素大或者小的元素,很明显这个题目就是这样。 使用单调栈来存储气温列表的索引,遍历气温列表,栈内存储未找到更高气温的索引,当遇到更高气温时,计算等待天数并更新结果列表。注意单调栈一般保存的是索引下标,这样方便更新每个位置的答案。关于单调栈:
使用场景
什么时候使用单调栈:通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了,时间复杂度是O(n);定义
单调栈 = 使用栈这个数据结构 + 压入弹出的规则由当前遍历到的元素和栈顶的元素的大小关系决定;
为什么叫做单调栈
如果是寻找右边第一个比自己大的元素,那么从栈顶到栈底是单调递增的;如果是寻找右边第一个比自己小的元素,那么从栈顶到栈底是单调递减的;
压入弹出的规则(以寻找右边第一个比自己大的元素为例)
- 当前元素大于栈顶元素,那么说明此时发现了栈顶元素的右边第一个比其大的元素,那么将当前元素保存到对应的结果上,同时将栈顶元素弹出,继续比较直至当前元素小于栈顶元素,将当前元素压入;
- 当前元素小于等于栈顶元素时,将当前元素直接压入
代码
class Solution: def dailyTemperatures(self, temperatures: List[int]) -> List[int]: n = len(temperatures) res = [0] * n s = [0] for i in range(1, n): while len(s)!=0 and temperatures[i] > temperatures[s[-1]]: res[s[-1]] = i - s[-1] s.pop() s.append(i) return res
5. 柱状图中最大的矩形
题目描述
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1。求在该柱状图中,能够勾勒出来的矩形的最大面积。解题思路
可以枚举以每个柱形为高度的最大矩形的面积。每个柱子向左向右分别查找第一个比他们矮的柱子,这两个柱子就是他们的左右边界。很明显左右边界可以用单调栈来查找;代码
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
left = [0] * n
right = [0] * n
s = []
for i in range(n):
if len(s) == 0 or heights[s[-1]] <= heights[i]:
s.append(i)
else:
while len(s) != 0 and heights[s[-1]] > heights[i]:
right[s[-1]] = i - 1 - s[-1]
s.pop()
s.append(i)
s = []
for i in range(n - 1, -1, -1):
if len(s) == 0 or heights[s[-1]] <= heights[i]:
s.append(i)
else:
while len(s) != 0 and heights[s[-1]] > heights[i]:
left[s[-1]] = s[-1] - i - 1
s.pop()
s.append(i)
res = 0
for i in range(n):
res = max(res, (left[i] + right[i] + 1) * heights[i])
return res
堆
定义
堆(heap)是一种满足特定条件的完全二叉树,主要可分为两种类型小顶堆(min heap):任意节点的值小于等于其子节点的值,根节点的值是堆中最小的。
大顶堆(max heap):任意节点的值大于等于其子节点的值,根节点的值是堆中最大的。
python中的堆
heapq:是最小堆
import heapq pri_que = [] heapq.heappush(pri_que, value) heapq.heappop(pri_que)
pri_que[0]是堆中的最小值
6. 数组中的第K个最大元素
- 题目描述
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 - 解题思路
使用最小堆来维护前 k 个最大的元素,遍历数组时,如果当前元素大于堆顶元素,则替换堆顶元素并调整堆。最终堆顶元素即为第 k 个最大元素。 - 代码
import heapq class Solution: def findKthLargest(self, nums: List[int], k: int) -> int: heap = [] n = len(nums) for i in range(n): if i < k: heapq.heappush(heap, nums[i]) else: if nums[i] > heap[0]: heapq.heappop(heap) heapq.heappush(heap, nums[i]) return heap[0]
7. 前 K 个高频元素
- 题目描述
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 - 解题思路
使用哈希表统计每个元素的频率,然后使用最小堆来维护频率前 k 高的元素。遍历哈希表时,如果当前元素频率大于堆顶元素,则替换堆顶元素并调整堆。这里有一个技巧,就是可以将频率和数组组成一个元组作为堆中存储的基本元素,因此元组也是可以排序的,这样的话就解决了多个数字对应相同频率的情况 - 代码
class Solution: def topKFrequent(self, nums: List[int], k: int) -> List[int]: dic = dict() for num in nums: dic[num] = dic.get(num, 0) + 1 pri_que = [] for key, value in dic.items(): heapq.heappush(pri_que, (value, key)) if len(pri_que) > k: heapq.heappop(pri_que) res = [] for i in range(k): res.append(pri_que[i][1]) return res
8. 数据流的中位数
题目描述
中位数是排序后数组的中间值。如果数组长度是偶数,中位数是中间两个数的平均值。设计一个支持以下两种操作的数据结构:addNum(num):从数据流中添加一个整数到数据结构中。findMedian():返回目前所有元素的中位数。解题思路
使用两个堆,一个最大堆存储较小的一半元素,一个最小堆存储较大的一半元素。添加元素时,根据元素大小分别插入对应的堆,并调整堆大小平衡,即两个堆中元素的数量差距不能大于1。中位数为两个堆顶元素的平均值或元素多的那个堆的堆顶元素。注意python中的heapq是最小堆,如果想实现最大堆,得把元素取反后压入
时间复杂度:addNum: O(logn),其中 n 为累计添加的数的数量。 findMedian: O(1)。
空间复杂度:O(n),主要为堆的开销。
代码
import heapq class MedianFinder: def __init__(self): self.l = [] self.r = [] def addNum(self, x): if len(self.l) == 0 and len(self.r) == 0: heapq.heappush(self.l, -x) elif x < -self.l[0]: heapq.heappush(self.l, -x) if len(self.l) - len(self.r) > 1: temp = -heapq.heappop(self.l) heapq.heappush(self.r, temp) else: heapq.heappush(self.r, x) if len(self.r) - len(self.l) > 1: temp = heapq.heappop(self.r) heapq.heappush(self.l, -temp) def findMedian(self): l_n = len(self.l) r_n = len(self.r) if l_n > r_n: return -self.l[0] elif l_n == r_n: return (-self.l[0] + self.r[0]) / 2.0 else: return self.r[0]