单调栈
顾名思义,首先是一个栈,然后在使用时遵循一定规律。一般是新元素入栈前,对比栈顶元素和新元素的关系,满足一定条件则不断弹出栈顶元素,做业务处理,然后才新元素入栈。
通常用于寻找下/前一个的更大/小元素,或者滑动窗口的最大/小值。
1. 下一个更大元素 II
难度
- Easy
- Medium
- Hard
完成情况
- 独立完成
- 参考思路
- 参考答案
思路
代码
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
stack = [] # 保存下标和元素
n = len(nums)
ans = [-1 for i in range(n)]
for i in range(2 * n):
j = i % n
while stack and stack[-1][1] < nums[j]:
originPos, _ = stack.pop()
ans[originPos] = nums[j]
stack.append([j, nums[j]])
return ans
2. 接雨水
难度
- Easy
- Medium
- Hard
完成情况
- 独立完成
- 参考思路
- 参考答案
思路
只有在遇到的下一个元素大于当前元素时,才有可能产生一个可以存水的图形,所以使用单调栈。
当左侧元素小于自身,右侧元素大于自身时,对存水量的贡献暂时为负。这部分需要好好体会。
代码
class Solution:
def trap(self, height: List[int]) -> int:
stack = []
ans = 0
n = len(height)
for index in range(n):
h = height[index]
while stack and height[stack[-1]] <= h:
rightEdge = stack.pop()
if stack:
ans += (min(height[stack[-1]], h) - height[rightEdge]) * (index - stack[-1] - 1)
stack.append(index)
return ans
3. 每日温度
难度
- Easy
- Medium
- Hard
完成情况
- 独立完成
- 参考思路
- 参考答案
思路
比较明显的可以用单调栈。
代码
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
n = len(temperatures)
ans = [0 for i in range(n)]
stack = [] # 存储下标
for index in range(n):
while stack and temperatures[stack[-1]] < temperatures[index]:
preDay = stack.pop()
ans[preDay] = index - preDay
stack.append(index)
return ans
4. 股票价格跨度
难度
- Easy
- Medium
- Hard
完成情况
- 独立完成
- 参考思路
- 参考答案
思路
要注意到当前元素的跨度跟之前元素的跨度之间的关系。
如果当前元素大于前一个元素,那么累加前一个元素的跨度,然后把前一个元素弹出。
代码
class StockSpanner:
__stack: List[List[int]]
def __init__(self):
self.__stack = []
def next(self, price: int) -> int:
res = 1
while self.__stack and self.__stack[-1][1] <= price:
res += self.__stack.pop()[0]
self.__stack.append([res, price])
return res
5. 超级丑数
难度
- Easy
- Medium
- Hard
完成情况
- 独立完成
- 参考思路
- 参考答案
思路
之前在写丑数时就考虑到更多质因数的情况,代码可以直接用了。生成新的丑数的方法是一样的,最小元素逐个乘以十质因数即可。就算再来超超超超超级丑数,也是一样做。
代码
class Solution:
def nthSuperUglyNumber(self, n: int, primes: List[int]) -> int:
hp = [1]
cnt = 0
records = set()
while True:
cur = heappop(hp)
cnt += 1
if cnt == n: return cur
for factor in primes:
num = cur * factor
if num not in records:
records.add(num)
heappush(hp, num)
6. 有序矩阵中第 K 小的元素
难度
- Easy
- Medium
- Hard
完成情况
- 独立完成
- 参考思路
- 参考答案
思路
会错意了,以为要时间复杂度小于 n 的平方。
直接维护一个固定大小的堆就好了。因为要的是第k小的元素,当大小大于k时,我们需要弹出最大元素,所以存储负值。那么最后堆顶元素的负值就是所求。
代码
class Solution:
def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
hp = []
n = len(matrix)
for i in range(n):
for j in range(n):
heappush(hp, -matrix[i][j])
if len(hp) > k:
heappop(hp)
return -hp[0]
7. 根据字符出现频率排序
难度
- Easy
- Medium
- Hard
完成情况
- 独立完成
- 参考思路
- 参考答案
思路
字符种类有限,先用定长数组统计字符频率,然后用两个元素的数组存储频率(负值)和字符(ascii)的对应关系,并插入堆中。最后逐个出堆并构造字符串。
代码
class Solution:
def frequencySort(self, s: str) -> str:
freq = [0 for i in range(123)]
hp = []
for ch in s: freq[ord(ch)] += 1
for i in range(48, 123):
if freq[i] != 0:
heappush(hp, [-freq[i], i])
s = ""
while hp:
num, ch = heappop(hp)
s += chr(ch) * (-num)
return s
8. 分割数组为连续子序列
难度
- Easy
- Medium
- Hard
完成情况
- 独立完成
- 参考思路
- 参考答案
思路
当遇到一个元素 num
时,我们优先考虑是否存在一个以 num - 1
结尾的序列,如果存在,应当将其加入到该序列末尾,且该序列最好是同类序列中最短的一个;否则,应当新建一个仅包含 num
的序列。最后,根据每个序列的长度,判断能否成功分割。如此一来,我们需要保存每个序列的末尾元素和长度信息(每次获取最小值)。可以使用一个字典,键为末尾元素,值为序列长度。
代码
class Solution:
def isPossible(self, nums: List[int]) -> bool:
dic = defaultdict(list)
for num in nums:
if dic[num - 1]:
len = heappop(dic[num - 1])
heappush(dic[num], len + 1)
else: heappush(dic[num], 1)
keys = dic.keys()
for key in keys:
if dic[key] and dic[key][0] < 3: return False
return True
Comments NOTHING