Leetcode刷题笔记—栈与队列

栈与队列

栈与队列是非常重要的基础数据结构,本文汇总了《代码随想录》和《Leetcode101》中关于栈与队列的练习题及其题解,旨在帮助读者更深入地理解相关概念和解题思路。如有疏漏或错误,恳请批评指正。

文章目录

    • 栈与队列
      • 1. 栈
        • [232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks)
        • [225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues)
        • [155. 最小栈](https://leetcode.cn/problems/min-stack/)
        • [20. 有效的括号](https://leetcode.cn/problems/valid-parentheses)
        • [1047. 删除字符串中的所有相邻重复项](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string)
        • [150. 逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation)
        • [735. 小行星碰撞](https://leetcode.cn/problems/asteroid-collision/)
        • [394. 字符串解码](https://leetcode.cn/problems/decode-string/)
        • [224. 基本计算器](https://leetcode.cn/problems/basic-calculator/)
        • [71. 简化路径](https://leetcode.cn/problems/simplify-path/)
      • 2. 队列
        • [933. 最近的请求次数](https://leetcode.cn/problems/number-of-recent-calls/)
        • [649. Dota2 参议院](https://leetcode.cn/problems/dota2-senate/)
        • [239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum)
      • 3. 优先队列
        • [347. 前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements)
        • [23. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/)
        • [218. 天际线问题](https://leetcode.cn/problems/the-skyline-problem)
        • [313. 超级丑数](https://leetcode.cn/problems/super-ugly-number)
        • [870. 优势洗牌](https://leetcode.cn/problems/advantage-shuffle)
      • 4. 单调栈
        • [739. 每日温度](https://leetcode.cn/problems/daily-temperatures)
        • [496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i)
        • [503. 下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii)
        • [901. 股票价格跨度](https://leetcode.cn/problems/online-stock-span/)
        • [42. 接雨水](https://leetcode.cn/problems/trapping-rain-water)
        • [84. 柱状图中最大的矩形](https://leetcode.cn/problems/largest-rectangle-in-histogram)

1. 栈

232. 用栈实现队列

问题描述:

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:

  • 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

Example:

输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

思路:使用两个栈来模拟队列,如果是push操作,就将元素添加到第一个栈s1,如果是pop或者top操作,如果第二个栈s2不为空,则弹出/返回栈顶元素,否则将s1中的元素依次移动到s2中然后再弹出栈顶元素

class MyQueue:def __init__(self):self.s1 = deque()self.s2 = deque()def push(self, x: int) -> None:self.s1.append(x)def pop(self) -> int:if self.s2:return self.s2.pop()while self.s1:self.s2.append(self.s1.pop())return self.s2.pop()def peek(self) -> int:top = self.pop()self.s2.append(top)return topdef empty(self) -> bool:return len(self.s1) == 0 and len(self.s2) == 0
225. 用队列实现栈

问题描述:

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false

注意:

  • 你只能使用队列的标准操作 —— 也就是 push to backpeek/pop from frontsizeis empty 这些操作。
  • 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

Example:

输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

思路:用两个队列模拟栈,如果是push操作,就将元素添加到第一个队列q1,如果是pop或者top操作,就将第一个队列中的队尾元素前的所有元素push到第二个队列q2中,然后弹出/返回q1的队首元素,完成操作后,交换q1q2的内容

class MyStack:def __init__(self):self.q1 = deque()self.q2 = deque()def push(self, x: int) -> None:self.q1.append(x)def pop(self) -> int:while len(self.q1) > 1:self.q2.append(self.q1.popleft())top = self.q1.popleft()self.q1, self.q2 = self.q2, self.q1return topdef top(self) -> int:while len(self.q1) > 1:self.q2.append(self.q1.popleft())top = self.q1.popleft()self.q2.append(top)self.q1, self.q2 = self.q2, self.q1return topdef empty(self) -> bool:return len(self.q1) == 0

优化,只使用一个队列

class MyStack:def __init__(self):self.q = deque()def push(self, x: int) -> None:self.q.append(x)def pop(self) -> int:n = len(self.q)for i in range(n - 1):self.q.append(self.q.popleft())top = self.q.popleft()return topdef top(self) -> int:n = len(self.q)for i in range(n - 1):self.q.append(self.q.popleft())top = self.q.popleft()self.q.append(top)return topdef empty(self) -> bool:return len(self.q) == 0
155. 最小栈

问题描述:

设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。

Example:

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]输出:
[null,null,null,null,-3,null,0,-2]解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

思路一: 额外建立一个新栈,栈顶表示原栈里所有值的最小值。每当在原栈里插入一个数字时,若该数字小于等于新栈栈顶,则表示这个数字在原栈里是最小值,我们将其同时插入新栈内。每当从原栈里取出一个数字时,若该数字等于新栈栈顶,则表示这个数是原栈里的最小值之一,我们同时取出新栈栈顶的值

class MinStack:def __init__(self):self.s1 = deque()self.s2 = deque()def push(self, val: int) -> None:self.s1.append(val)if len(self.s2) == 0 or val < self.s2[-1]:self.s2.append(val)def pop(self) -> None:top = self.s1.pop()if top == self.s2[-1]:self.s2.pop()def top(self) -> int:return self.s1[-1]def getMin(self) -> int:return self.s2[-1]

思路二:如果要求不能用额外的栈呢?我们可以用一个全局变量min保存当前栈中的最小值,并且此时栈中不再存放元素的值,而是存放当前元素与栈中最小值的差

  • 如果遇到push操作,计算元素 v a l val val与最小值的差值 d i f f diff diff,如果 d i f f < 0 diff < 0 diff<0,说明该元素比栈中最小值更小,此时需要更新栈中的最小值 m i n = m i n − d i f f min = min -diff min=mindiff,并将差值压入栈中,如果 d i f f ≥ 0 diff \ge 0 diff0,说明该元素不是最小值,直接将差值压入栈中
  • 如果遇到pop操作,首先判断栈顶元素 d i f f diff diff的符号,如果 d i f f < 0 diff < 0 diff<0,说明弹出的栈顶元素是当前的最小值,因此需要更新栈中的最小值 m i n = m i n − d i f f min = min - diff min=mindiff,如果 d i f f ≥ 0 diff \ge 0 diff0,则pop操作不会影响栈中的最小值
  • 如果遇到top操作,判断栈顶元素 d i f f diff diff的符号,如果 d i f f < 0 diff < 0 diff<0,说明栈顶元素是当前的最小值,直接返回 m i n min min的值即可,如果 d i f f ≥ 0 diff \ge 0 diff0,我们可以通过该差值回复原始的元素值 m i n + d i f f min + diff min+diff
  • 对于getMin操作,直接返回 m i n min min即可
class MinStack:def __init__(self):self.s = deque()self.min = 0def push(self, val: int) -> None:if not self.s:self.s.append(0)self.min = valelse:diff = val - self.minself.s.append(diff)if diff < 0:self.min = valdef pop(self) -> None:if self.s:diff = self.s.pop()if diff < 0:self.min -= diffdef top(self) -> int:if self.s[-1] > 0:return self.min + self.s[-1] else:return self.mindef getMin(self) -> int:return self.min
20. 有效的括号

问题描述:

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

Example:

**输入:**s = “()[]{}”

**输出:**true

思路:用一个栈来存放等待匹配的左括号,如果遍历到左括号,压入栈中;如果遇到右括号,判断是否能与栈顶元素匹配,如果可以则将栈顶元素弹出,否则返回False,当遍历完字符串后,如果栈为空则说明所有的左括号都能被匹配,返回True,否则返回False

class Solution:def isValid(self, s: str) -> bool:stack = deque()for ss in s:if ss in ['(', '[', '{']:stack.append(ss)else:if len(stack) == 0:return Falsetop = stack[-1]if ss == ')' and top == '(':stack.pop()elif ss == ']' and top == '[':stack.pop()elif ss == '}' and top == '{':stack.pop()else:return Falsereturn len(stack) == 0
1047. 删除字符串中的所有相邻重复项

问题描述:

给出由小写字母组成的字符串 s重复项删除操作会选择两个相邻且相同的字母,并删除它们。

s 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

Example:

输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

思路:用一个栈存放结果,如果当前字符和栈顶元素相同,则将栈顶元素弹出

class Solution:def removeDuplicates(self, s: str) -> str:stack = deque()for ss in s:if len(stack) == 0:stack.append(ss)else:top = stack[-1]if top == ss:stack.pop()else:stack.append(ss)return ''.join(list(stack))
150. 逆波兰表达式求值

问题描述:

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*''/'
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

Example:

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

思路:用一个栈来存放数值,当遇到数字时,压入栈中,当遇到+-*/等符号时从数值栈中取出栈顶的两个元素,计算出结果后再压入栈中

class Solution:def evalRPN(self, tokens: List[str]) -> int:num_stack = deque()i = 0for token in tokens:if token not in ['+', '-', '*', '/']:num_stack.append(int(token))else:b = num_stack.pop()a = num_stack.pop()if token == '+':res = a + belif token == '-':res = a - belif token == '*':res = a * belse:res = int(a / b)num_stack.append(res)return num_stack[-1]
735. 小行星碰撞

问题描述:

给定一个整数数组 asteroids,表示在同一行的小行星。

对于数组中的每一个元素,其绝对值表示小行星的大小,正负表示小行星的移动方向(正表示向右移动,负表示向左移动)。每一颗小行星以相同的速度移动。

找出碰撞后剩下的所有小行星。碰撞规则:两个小行星相互碰撞,较小的小行星会爆炸。如果两颗小行星大小相同,则两颗小行星都会爆炸。两颗移动方向相同的小行星,永远不会发生碰撞。

Example:

输入:asteroids = [5,10,-5]
输出:[5,10]
解释:10 和 -5 碰撞后只剩下 10 。 5 和 10 永远不会发生碰撞。

思路:如果栈为空,直接压入栈,否则判断栈顶元素 s t a c k [ − 1 ] stack[-1] stack[1]和当前元素 c u r cur cur的正负号,如果 s t a c k [ − 1 ] > 0 stack[-1] > 0 stack[1]>0并且 c u r < 0 cur < 0 cur<0,说明会发生碰撞,此时需要判断双方的大小关系

  • a b s ( s t a c k [ − 1 ] ) > a b s ( c u r ) abs(stack[-1]) > abs(cur) abs(stack[1])>abs(cur),当前的行星会爆炸
  • a b s ( s t a c k [ − 1 ] ) = a b s ( c u r ) abs(stack[-1]) = abs(cur) abs(stack[1])=abs(cur),两个行星都会爆炸
  • a b s ( s t a c k [ − 1 ] ) < a b s ( c u r ) abs(stack[-1]) < abs(cur) abs(stack[1])<abs(cur),前一颗行星会爆炸

这里需要用一个while循环来持续监测当前行星是否会和前一颗行星爆炸,如果前一颗行星爆炸,则当前行星继续和更前方的行星进行碰撞,直到不会发生碰撞为止

class Solution:def asteroidCollision(self, asteroids: List[int]) -> List[int]:n = len(asteroids)stack = deque()for i in range(n):if not stack:stack.append(asteroids[i])continuecur = asteroids[i]while stack and stack[-1] > 0 and cur < 0:if abs(stack[-1]) == abs(cur):cur = 0elif abs(stack[-1]) > abs(cur):cur = stack[-1]else:cur = curstack.pop()if cur != 0:stack.append(cur)return list(stack)
394. 字符串解码

问题描述:

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a2[4] 的输入。

Example:

输入:s = "3[a]2[bc]"
输出:"aaabcbc"输入:s = "3[a2[c]]"
输出:"accaccacc"

思路一:栈中存放正在解码的子字符串,并用一个flag​变量记录k[]的嵌套层数,如果遇到[,直接入栈并将flag加一;如果遇到数字,则解析数字并将其入栈;如果遇到],则逐个弹出栈中的元素直到遇到前一个[,将这些元素拼接起来构成子串,然后再从栈中取出重复的次数 k k k并将子串重复 k k k次,如果此时栈为空,则将重复后的子串拼接到结果中,否则将其入栈,然后将flag减一;如果遇到字母,当flag为0时直接拼接到结果中,否则将其入栈

class Solution:def decodeString(self, s: str) -> str:n = len(s)stack = deque()ans = ""flag = 0i = 0while i < n:if s[i] == ']':sub_s = ''while stack and stack[-1] != '[':sub_s = stack.pop() + sub_sstack.pop()num = stack.pop()sub_s = sub_s * numif stack:stack.append(sub_s)else:ans += sub_sflag -=1elif s[i] == '[':stack.append(s[i])elif s[i].isdigit():num = 0while s[i].isdigit():num = num * 10 + int(s[i])i += 1stack.append(num)flag += 1continueelse:if flag == 0:ans += s[i]else:stack.append(s[i])i += 1return ans 

思路二:栈中同时存放解码后和正在解码的子串,无需flag变脸来记录嵌套层数,遇到 [ [ [或者字母时,直接入栈,遇到数字时,解析数字后再入栈,遇到 ] ] ]时,则逐个弹出栈中的元素直到遇到前一个[,将这些元素拼接起来构成子串,然后再从栈中取出重复的次数 k k k,并将子串重复 k k k次后再加入栈中,当遍历完字符串后,将战中的子串拼接起来即可

class Solution:def decodeString(self, s: str) -> str:n = len(s)stack = deque()i = 0while i < n:if s[i].isdigit():num = 0while i < n and s[i].isdigit():num = num * 10 + int(s[i])i += 1stack.append(num)continueelif ord('a') <= ord(s[i]) <= ord('z'):stack.append(s[i])elif s[i] == '[':stack.append(s[i])elif s[i] == ']':ss = ""while stack and stack[-1] != '[':ss = stack.pop() + ssstack.pop()ss = ss * stack.pop()stack.append(ss)i += 1return ''.join(stack)
224. 基本计算器

问题描述:

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()

Example:

输入:s = "(1+(4+5+2)-3)+(6+8)"
输出:23

思路一:括号展开

class Solution:def calculate(self, s: str) -> int:n = len(s)sign = 1stack = deque([1])res = 0i = 0while i < n:if s[i] == ' ':i += 1elif s[i] == '+':sign = stack[-1]i += 1elif s[i] == '-':sign = -stack[-1]i += 1elif s[i] == '(':stack.append(sign)i += 1elif s[i] == ')':stack.pop()i += 1else:num = 0while i < len(s) and s[i].isdigit():num = num * 10 + int(s[i])i += 1res = res + sign * numreturn res

思路二:中缀表达式转后缀表达式,需要对前导符号进行额外的处理

class Solution:def calculate(self, s: str) -> int:s = s.replace(' ', '')n = len(s)rpn = []stack = deque()priority = {'+': 1,'-': 1,'*': 2,'/': 2,'@': 3}i = 0while i < n:if s[i].isdigit():num = 0while i < n and s[i].isdigit():num = num * 10 + int(s[i])i += 1rpn.append(str(num))continueelif s[i] == '(':stack.append(s[i])elif s[i] == ')':while stack and stack[-1] != '(':rpn.append(stack.pop())stack.pop()else:if s[i] == '-':if i == 0 or s[i - 1] == '(':stack.append('@')i += 1continuewhile stack and stack[-1] != '(' and priority[stack[-1]] >= priority[s[i]]:rpn.append(stack.pop())stack.append(s[i])i += 1while stack:rpn.append(stack.pop())return self.calc(rpn)def calc(self, tokens):n = len(tokens)ans = 0stack = deque()i = 0while i < n:if tokens[i] not in ['+', '-', '*', '/', '@']:stack.append(int(tokens[i]))elif tokens[i] == '@':stack.append(-stack.pop())else:b = stack.pop()a = stack.pop()if tokens[i] == '+':res = a + belif tokens[i] == '-':res = a - belif tokens[i] == '*':res = a * belse:res = int(a / b)stack.append(res)i += 1return stack[-1]

简洁版

class Solution:def calculate(self, s: str) -> int:s = s.replace(' ', '').replace('(+', '(0+').replace('(-', '(0-')n = len(s)priority = {'+': 1,'-': 1,'*': 2,'/': 2,'^': 3}num_stack = deque([0])op_stack = deque()i = 0while i < n:if s[i].isdigit():num = 0while i < n and s[i].isdigit():num = num * 10 + int(s[i])i += 1num_stack.append(num)continueelif s[i] == '(':op_stack.append(s[i])elif s[i] == ')':while op_stack and op_stack[-1] != '(':self.calc(num_stack, op_stack)op_stack.pop()else:while op_stack and op_stack[-1] != '(' and priority[op_stack[-1]] >= priority[s[i]]:self.calc(num_stack, op_stack)op_stack.append(s[i])i += 1while op_stack:self.calc(num_stack, op_stack)return num_stack[-1]def calc(self, num_stack, op_stack):b = num_stack.pop()a = num_stack.pop()op = op_stack.pop()if op == '+':res = a + belif op == '-':res = a - belif op == '*':res = a * belif op == '/':res = a / belif op == '^':res = a ** bnum_stack.append(int(res))return num_stack[-1]
71. 简化路径

问题描述:

给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/' 开头),请你将其转化为 更加简洁的规范路径

在 Unix 风格的文件系统中规则如下:

  • 一个点 '.' 表示当前目录本身。
  • 此外,两个点 '..' 表示将目录切换到上一级(指向父目录)。
  • 任意多个连续的斜杠(即,'//''///')都被视为单个斜杠 '/'
  • 任何其他格式的点(例如,'...''....')均被视为有效的文件/目录名称。

返回的 简化路径 必须遵循下述格式:

  • 始终以斜杠 '/' 开头。
  • 两个目录名之间必须只有一个斜杠 '/'
  • 最后一个目录名(如果存在)不能'/' 结尾。
  • 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 '.''..')。

返回简化后得到的 规范路径

Example:

**输入:**path = “/…/a/…/b/c/…/d/./”

输出:“/…/b/d”

思路:

class Solution:def simplifyPath(self, path: str) -> str:n = len(path)stack = deque()names = path.split("/")for name in names:if name == '' or name == '.':continueelif name == '..':if stack:stack.pop()else:stack.append(name)return '/' + '/'.join(stack)

2. 队列

933. 最近的请求次数

问题描述:

写一个 RecentCounter 类来计算特定时间范围内最近的请求。

请你实现 RecentCounter 类:

  • RecentCounter() 初始化计数器,请求数为 0 。
  • int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。

保证 每次对 ping 的调用都使用比之前更大的 t 值。

Example:

输入:
["RecentCounter", "ping", "ping", "ping", "ping"]
[[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1);     // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100);   // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001);  // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002);  // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3

思路:队列的特点是先进先出,所以当一个新的请求到来时,最先过期的是队首元素,所以我们只需要从队首元素开始遍历,只要请求时间小于 t − 3000 t-3000 t3000,我们就将其从队列中删除,这样队列中剩下的元素个数就是 [ t − 3000 , t ] [t-3000, t] [t3000,t]内发生的请求数

class RecentCounter:def __init__(self):self.q = deque()def ping(self, t: int) -> int:self.q.append(t)while self.q and self.q[0] < t - 3000:self.q.popleft()return len(self.q)
649. Dota2 参议院

问题描述:

Dota2 的世界里有两个阵营:Radiant(天辉)和 Dire(夜魇)

Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定。他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的 项:

  • 禁止一名参议员的权利:参议员可以让另一位参议员在这一轮和随后的几轮中丧失 所有的权利
  • 宣布胜利:如果参议员发现有权利投票的参议员都是 同一个阵营的 ,他可以宣布胜利并决定在游戏中的有关变化。

给你一个字符串 senate 代表每个参议员的阵营。字母 'R''D'分别代表了 Radiant(天辉)和 Dire(夜魇)。然后,如果有 n 个参议员,给定字符串的大小将是 n

以轮为基础的过程从给定顺序的第一个参议员开始到最后一个参议员结束。这一过程将持续到投票结束。所有失去权利的参议员将在过程中被跳过。

假设每一位参议员都足够聪明,会为自己的政党做出最好的策略,你需要预测哪一方最终会宣布胜利并在 Dota2 游戏中决定改变。输出应该是 "Radiant""Dire"

Example:

输入:senate = "RDD"
输出:"Dire"
解释:
第 1 轮时,第一个来自 Radiant 阵营的参议员可以使用第一项权利禁止第二个参议员的权利。
这一轮中,第二个来自 Dire 阵营的参议员会将被跳过,因为他的权利被禁止了。
这一轮中,第三个来自 Dire 阵营的参议员可以使用他的第一项权利禁止第一个参议员的权利。
因此在第二轮只剩下第三个参议员拥有投票的权利,于是他可以宣布胜利

思路:用两个队列q1q2分别存放Radiant阵营和Dire阵营的参议员的位置(即先后顺序),同时遍历两个队列,如果 q 1 [ 0 ] < q 2 [ 0 ] q1[0]<q2[0] q1[0]<q2[0],说明Radiant阵营的参议员在Dire阵营的参议员的前面,此时最好的策略是禁止Dire参议员的权利,也就是直接从队列中移除,而Radiant阵营的参议员本轮已经没有权利需要进入下一轮投票,所以我们将其添加到队列尾,同时将其位置加 n n n n n n是两个队列的长度和),这样就可以保证本轮中位置靠后的Dire议员可以禁止本轮已经行使过权利的Radiant议员; q 1 [ 0 ] > q 2 [ 0 ] q1[0]>q2[0] q1[0]>q2[0]的情况同理,如此循环下去直到某一个队列为空,这样另一个非空队列所代表的阵营就可以宣布胜利

class Solution:def predictPartyVictory(self, senate: str) -> str:q1, q2 = deque(), deque()for i in range(len(senate)):if senate[i] == 'R':q1.append(i)else:q2.append(i)n = len(q1) + len(q2)while q1 and q2:top1 = q1.popleft()top2 = q2.popleft()if top1 < top2:q1.append(top1 + n)else:q2.append(top2 + n)if q1:return 'Radiant'if q2:return 'Dire'
239. 滑动窗口最大值

问题描述:

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

Example:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       31 [3  -1  -3] 5  3  6  7       31  3 [-1  -3  5] 3  6  7       51  3  -1 [-3  5  3] 6  7       51  3  -1  -3 [5  3  6] 7       61  3  -1  -3  5 [3  6  7]      7

思路:滑动窗口本质上可以看作是一个队列,元素是先进先出的顺序,因此滑动窗口的最大值就是求当前队列的最大值,看到这个问题很容易联想到155. 最小栈,我们是否也可以额外维护一个队列来存放最大值呢?实际上不太好实现,因为栈的插入和删除都是对同一端进行操作,当插入元素时,我们可以对比栈中的最小值和插入元素来获取插入后的最小值,当删除元素时,直接从栈中弹出就可以了,因为元素 s t a c k [ i ] stack[i] stack[i]的删除不会对最小栈 [ 0 , i − 1 ] [0,i-1] [0,i1]范围中的最小值产生影响。但是队列就不可以了,由于队列的插入和删除都是分别对右端和左端进行操作,假如用一个最小队列来存放原始队列 [ 0 , i ] [0,i] [0,i]范围内的最小值,那么删除元素时可能会对最小队列 [ 1 , i ] [1,i] [1,i]范围中的最小值产生影响,所以我们需要换一种思路

既然队列的特点是先入先出,那么当窗口向右移动时,我们将窗口左端的值从队列左端删除,同时从队尾开始将队列中所有小于窗口右端的元素弹出,这样队列的最左端就永远是当前窗口内的最大值 ,这道题也是单调栈的一种延申:该双端队列利用从左到右递减来维持大小关系

class Solution:def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:n = len(nums)q = deque()ans = []for i in range(n):if i < k:while q and nums[i] > q[-1]:q.pop()q.append(nums[i])else:ans.append(q[0])# 弹出逻辑if nums[i - k] == q[0]:q.popleft()# 放入逻辑while q and nums[i] > q[-1]:q.pop()q.append(nums[i])ans.append(q[0])return ans

3. 优先队列

优先队列的实现

class MinHeap:def __init__(self):self.heap = []def swim(self, pos):parent = (pos - 1) >> 1while parent >= 0 and self.heap[pos] < self.heap[parent]:self.heap[parent], self.heap[pos] = self.heap[pos], self.heap[parent]pos = (pos - 1) >> 1parent = (parent - 1) >> 1def sink(self, pos):n = len(self.heap)while 2 * pos + 1 < n:child = 2 * pos + 1if child + 1 < n and self.heap[child] > self.heap[child + 1]:child += 1 if self.heap[pos] <= self.heap[child]:breakself.heap[pos], self.heap[child] = self.heap[child], self.heap[pos]pos = childdef push(self, val):n = len(self.heap)self.heap.append(val)self.swim(n)def pop(self):n = len(self.heap)top = self.heap[0]self.heap[0] = self.heap[n - 1]self.heap.pop()self.sink(0)return topdef top(self):return self.heap[0]def __len__(self):return len(self.heap)def heap_sort(nums):heap = MinHeap()for num in nums:heap.push(num)ans = []while len(heap) > 0:ans.append(heap.pop())return ans
347. 前 K 个高频元素

问题描述:

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

Example:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

思路:首先遍历整个数组,并使用哈希表记录每个数字出现的次数,并形成一个「出现次数数组」。找出原数组的前 k k k个高频元素,就相当于找出「出现次数数组」的前 k k k大的值

最简单的做法是给「出现次数数组」排序。但由于可能有 O ( N ) O(N) O(N)个不同的出现次数(其中 N N N为原数组长度),故总的算法复杂度会达到 O ( N l o g N ) O(NlogN) O(NlogN),不满足题目的要求

在这里,我们可以利用堆的思想:建立一个小顶堆,然后遍历「出现次数数组」:

  • 如果堆的元素个数小于 k k k,就可以直接插入堆中
  • 如果堆的元素个数等于 k k k,则检查堆顶与当前出现次数的大小。如果堆顶更大,说明至少有 k k k个数字的出现次数比当前值大,故舍弃当前值;否则,就弹出堆顶,并将当前值插入堆中

遍历完成后,堆中的元素就代表了「出现次数数组」中前 k k k大的值

class Solution:def topKFrequent(self, nums: List[int], k: int) -> List[int]:n = len(nums)heap = []freq = Counter(nums)for key, value in freq.items():if len(heap) == k:if value > heap[0][0]:heapq.heapreplace(heap, [value, key])else:heapq.heappush(heap, [value, key])ans = []for i in range(k):ans.append(heapq.heappop(heap)[1])return ans
23. 合并 K 个升序链表

问题描述:

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

Example:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[1->4->5,1->3->4,2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

思路一:从数组中取出两个链表,然后将其合并为一个新的升序链表然后放入数组中,重复此过程直到数组中只剩下一个链表,该链表就是所求的合并链表

class Solution:def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:if len(lists) == 1:return lists[0]r = Nonewhile len(lists) > 1:p = lists.pop()q = lists.pop()r = self.merge(p, q)lists.append(r)return rdef merge(self, p, q):dummy = ListNode()r = dummywhile p and q:if p.val < q.val:r.next = ListNode(p.val)p = p.nextelse:r.next = ListNode(q.val)q = q.nextr = r.nextif p:r.next = pif q:r.next = qreturn dummy.next

思路二:用小根堆存放数组中所有链表的头节点,每次从小根堆中取出值最小的头节点,加入合并链表中,如果头节点的下一个节点不为空,就然后将下一个节点重新放入小根堆中,重复此过程直到堆为空

class Solution:def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:dummy = ListNode()p = dummyheap = []for h in lists:if h:heapq.heappush(heap, [h.val, id(h), h])while heap:v, _, h = heapq.heappop(heap)p.next = hp = p.next if h.next:heapq.heappush(heap, [h.next.val, id(h.next),h.next])return dummy.next
218. 天际线问题

问题描述:

城市的 天际线 是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。给你所有建筑物的位置和高度,请返回 由这些建筑物形成的 天际线

每个建筑物的几何信息由数组 buildings 表示,其中三元组 buildings[i] = [lefti, righti, heighti] 表示:

  • lefti 是第 i 座建筑物左边缘的 x 坐标。
  • righti 是第 i 座建筑物右边缘的 x 坐标。
  • heighti 是第 i 座建筑物的高度。

你可以假设所有的建筑都是完美的长方形,在高度为 0 的绝对平坦的表面上。

天际线 应该表示为由 “关键点” 组成的列表,格式 [[x1,y1],[x2,y2],...] ,并按 x 坐标 进行 排序关键点是水平线段的左端点。列表中最后一个点是最右侧建筑物的终点,y 坐标始终为 0 ,仅用于标记天际线的终点。此外,任何两个相邻建筑物之间的地面都应被视为天际线轮廓的一部分。

**注意:**输出天际线中不得有连续的相同高度的水平线。例如 [...[2 3], [4 5], [7 5], [11 5], [12 7]...] 是不正确的答案;三条高度为 5 的线应该在最终输出中合并为一个:[...[2 3], [4 5], [12 7], ...]

Example:

输入:buildings = [[2,9,10],[3,7,15],[5,12,12],[15,20,10],[19,24,8]]
输出:[[2,10],[3,15],[7,12],[12,0],[15,10],[20,8],[24,0]]
解释:
图 A 显示输入的所有建筑物的位置和高度,
图 B 显示由这些建筑物形成的天际线。图 B 中的红点表示输出列表中的关键点。

思路:翻译一下题目,假设你处于一个虚拟世界,buildings中包含了每个种族的[出现时间, 灭绝时间, 强度],在同一时间中只有强度最高的种族才能够统治世界,我们要做的就是当某个种族出现或者灭亡时,判断当前统治世界的种族,如果统治世界的种族发生了变化,我们就将新的种族放入到结果集中,这里我们默认一直存在一个强度为0的种族,当其他种族都灭绝时,由它来进行统治世界(对应于两个相邻建筑物之间的地面)

由于统治世界的种族发生变化的时机出现在某个种族的出现时间或灭绝时间,因此我们用一个数组来存放每个种族的出现/灭亡时间和对应的强度,为了方便排序,对于出现时间,令强度为负;对于灭绝时间,令强度为正,然后我们对数组按升序排序,这样我们就可以按照时间顺序遍历所有种族的出现和灭绝时间

如果我们遇到某个种族的出现时间,我们将其强度加入大根堆,否则我们需要将当前种族的强度从大根堆中删除。然后我们获取大根堆中的堆顶元素,该元素代表了当前统治世界的种族强度,如果结果集为空或者该强度和结果集最后一个种族的强度不同,我们就将[当前时间, 强度]加入结果集中,表示新的种族开始统治世界,当遍历完数组后,结果集中存放的就是所求的答案

这里有个问题是如果我们需要将当前种族的强度从大根堆中删除,需要先查找然后再删除,时间复杂度较高,我们可以用一个哈希表记录强度所对应的删除次数,如果某个种族灭绝了,我们就将该种族对应的强度的删除次数加一,这样再取堆顶元素之前,我们可以判断当前堆顶元素的删除次数是否大于等于1,如果是,我们就要将堆顶元素弹出,然后将哈希表中对应的删除次数减一,重复这个过程,直到堆顶元素的删除次数为0

class Solution:def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:n = len(buildings)ans = []points = []for building in buildings:points.append([building[0], -building[2]])points.append([building[1], building[2]])points = sorted(points, key=lambda x: (x[0], x[1]))_hash = {}max_heap = [0]for i in range(len(points)):point, height = points[i]# 如果是左端点 将新的种族加入堆if height < 0:heapq.heappush(max_heap, height)# 如果是右端点 删除堆中结束统治的种族else:if height not in _hash:_hash[height] = 1else:_hash[height] += 1while max_heap:top = -max_heap[0]if top in _hash:if _hash[top] == 1:del _hash[top]else:_hash[top] -= 1heapq.heappop(max_heap)else:break # 获取堆顶元素 即当前处于统治的种族h = -max_heap[0]if not ans or ans[-1][1] != h:ans.append([points[i][0], h])return ans 
313. 超级丑数

问题描述:

超级丑数 是一个正整数,并满足其所有质因数都出现在质数数组 primes 中。

给你一个整数 n 和一个整数数组 primes ,返回第 n超级丑数

题目数据保证第 n超级丑数32-bit 带符号整数范围内。

Example:

输入:n = 12, primes = [2,7,13,19]
输出:32 
解释:给定长度为 4 的质数数组 primes = [2,7,13,19],前 12 个超级丑数序列为:[1,2,4,7,8,13,14,16,19,26,28,32] 。

思路:我们可以用一个优先队列存放当前已经求出的超级丑数(初始化为[1]),然后每轮循环从队头取出一个元素 h e a p [ 0 ] heap[0] heap[0],用质数数组中的元素 p r i m e s [ i ] primes[i] primes[i]与其组合成 p r i m e s [ i ] ∗ h e a p [ 0 ] primes[i]*heap[0] primes[i]heap[0]并加入队列,直到队头元素 h e a p [ 0 ] heap[0] heap[0]可以被当前的质数 p r i m e s [ i ] primes[i] primes[i]整除,这样循环 n − 1 n-1 n1次后拿到的的队头元素就是第 n n n个超级丑数

为什么 h e a p [ 0 ] % p r i m e s [ i ] = = 0 heap[0]\ \% \ primes[i] == 0 heap[0] % primes[i]==0的时候停止当前轮的循环呢?因为继续循环下去可能导致同一丑数多次进队!

假设 h e a p [ 0 ] = p r i m e s [ i ] ∗ a heap[0] = primes[i] * a heap[0]=primes[i]a,由于超级丑数 h e a p [ 0 ] heap[0] heap[0]的质因数都出现在 p r i m e s primes primes中,所以 a ∈ p r i m e s a \in primes aprimes,而下一个质数 p r i m e s [ i + 1 ] primes[i + 1] primes[i+1] h e a p [ 0 ] heap[0] heap[0]进行组合的数就可以展开为$primes[i + 1] * primes[i] * a $ ,这说明 p r i m e s [ i + 1 ] primes[i + 1] primes[i+1] h e a p [ 0 ] heap[0] heap[0]进行组合的数也可以用 p r i m e s [ i ] primes[i] primes[i] p r i m e s [ i + 1 ] ∗ a primes[i+1] * a primes[i+1]a进行组合得到,又因为 a a a p r i m e s primes primes中,所以 a a a必定是超级丑数,所以 p r i m e s [ i + 1 ] primes[i+1] primes[i+1]是有可能和 a a a组合构成 p r i m e s [ i + 1 ] ∗ a primes[i+1] * a primes[i+1]a的,因此可能会出现重复的超级丑数

class Solution:def nthSuperUglyNumber(self, n: int, primes: List[int]) -> int:heap = [1]for i in range(n - 1):top = heap[0]heapq.heappop(heap)for prime in primes:heapq.heappush(heap, prime * top)if top % prime == 0:breakreturn heap[0]
870. 优势洗牌

问题描述:

给定两个长度相等的数组 nums1nums2nums1 相对于 nums2优势可以用满足 nums1[i] > nums2[i] 的索引 i 的数目来描述。

返回 nums1任意排列,使其相对于 nums2 的优势最大化。

Example:

输入:nums1 = [2,7,11,15], nums2 = [1,10,4,11]
输出:[2,11,7,15]

思路一:

class Solution:def advantageCount(self, nums1: List[int], nums2: List[int]) -> List[int]:n = len(nums1)lmt = 1e9heap1, heap2 = [], []for i in range(n):heapq.heappush(heap1, nums1[i])heapq.heappush(heap2, [nums2[i], i])ans = [0] * nwhile heap1 and heap2:top1 = heap1[0]top2 = heap2[0]if top1 > top2[0] or top1 >= lmt:if top1 >= lmt:top1 -= lmtans[top2[1]] = top1heapq.heappop(heap1)heapq.heappop(heap2)else:heapq.heappop(heap1)heapq.heappush(heap1, top1 + lmt)return ans

思路二:贪心

class Solution:def advantageCount(self, nums1: List[int], nums2: List[int]) -> List[int]:n = len(nums1)p = deque(sorted(nums1, reverse=True))q = sorted(range(n), key=lambda x: nums2[x], reverse=True)ans = [0] * nfor i in range(n):if p[0] > nums2[q[i]]:ans[q[i]] = p.popleft()else:ans[q[i]] = p.pop()return ans

4. 单调栈

单调栈通过维持栈内值的单调递增(递减)性,在整体 O ( n ) O(n) O(n) 的时间内处理需要大小比较的问题

遇到求下一个最小/最大元素的问题时要第一时间想到单调栈!

739. 每日温度

问题描述:

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

Example:

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

思路:单调栈的经典题目,维持栈内的单调递减性质,如果当前元素小于栈顶元素,则不改变栈内的单调递减性,直接将当前元素加入栈,如果当前元素大于栈顶元素,说明出现了更高的温度,将栈顶元素弹出后继续用新的栈顶元素与当前元素比较,直到栈为空或者当前元素不大于栈顶元素,此时再将当前元素加入栈

class Solution:def dailyTemperatures(self, temperatures: List[int]) -> List[int]:n = len(temperatures)ans = [0] * nstack = deque()stack.append(0)for i in range(1, n):while stack and temperatures[stack[-1]] < temperatures[i]:ans[stack[-1]] = i - stack[-1]stack.pop()stack.append(i)return ans
496. 下一个更大元素 I

问题描述:

nums1 中数字 x下一个更大元素 是指 xnums2 中对应位置 右侧第一个x 大的元素。

给你两个 没有重复元素 的数组 nums1nums2 ,下标从 0 开始计数,其中nums1nums2 的子集。

对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j]下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1

返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素

Example:

输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
- 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。
- 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。

思路:单调栈+哈希表,首先遍历 n u m s 2 nums2 nums2,用单调栈求出每一个元素的下一个更大元素,将结果存放在哈希表中,然后再遍历 n u m s 1 nums1 nums1,根据元素的值直接从哈希表中取出在 n u m s 2 nums2 nums2中对应位置右侧下一个更大的元素

总的时间复杂度为 O ( m + n ) O(m+n) O(m+n),其中 m m m n n n为数组 n u m s 1 nums1 nums1 n u m s 2 nums2 nums2的长度

class Solution:def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:m, n = len(nums1), len(nums2)stack = deque()next_greater = dict.fromkeys(nums2, -1)for i in range(n):while stack and nums2[stack[-1]] < nums2[i]:next_greater[nums2[stack[-1]]] = nums2[i]stack.pop()stack.append(i)ans = []for i in range(m):ans.append(next_greater[nums1[i]])return ans
503. 下一个更大元素 II

问题描述:

给定一个循环数组 numsnums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素

数字 x下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1

Example:

输入: nums = [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数; 
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

思路一:将两个 n u m s nums nums数组拼接在一起,使用单调栈计算出每一个元素的下一个最大值,最后只需要返回结果集中第一个 n u m s nums nums数组的部分

class Solution:def nextGreaterElements(self, nums: List[int]) -> List[int]:n = len(nums)nums = nums[:] + nums[:-1]stack = deque()next_greater = [-1] * nfor i in range(len(nums)):while stack and nums[stack[-1]] < nums[i]:if stack[-1] < n:next_greater[stack[-1]] = nums[i]stack.pop()stack.append(i)return next_greater

思路二:不拼接数组,手动模拟循环的过程,相当于遍历两遍原数组,用取模操作来获取数组索引

class Solution:def nextGreaterElements(self, nums: List[int]) -> List[int]:n = len(nums)stack = deque()next_greater = [-1] * nfor i in range(2 * len(nums) - 1):while stack and nums[stack[-1]] < nums[i % n]:next_greater[stack[-1]] = nums[i % n]stack.pop()stack.append(i % n)return next_greater
901. 股票价格跨度

问题描述:

设计一个算法收集某些股票的每日报价,并返回该股票当日价格的 跨度

当日股票价格的 跨度 被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。

  • 例如,如果未来 7 天股票的价格是 [100,80,60,70,60,75,85],那么股票跨度将是 [1,1,1,2,1,4,6]

实现 StockSpanner 类:

  • StockSpanner() 初始化类对象。
  • int next(int price) 给出今天的股价 price ,返回该股票当日价格的 跨度

Example:

输入:
["StockSpanner", "next", "next", "next", "next", "next", "next", "next"]
[[], [100], [80], [60], [70], [60], [75], [85]]
输出:
[null, 1, 1, 1, 2, 1, 4, 6]解释:
StockSpanner stockSpanner = new StockSpanner();
stockSpanner.next(100); // 返回 1
stockSpanner.next(80);  // 返回 1
stockSpanner.next(60);  // 返回 1
stockSpanner.next(70);  // 返回 2
stockSpanner.next(60);  // 返回 1
stockSpanner.next(75);  // 返回 4 ,因为截至今天的最后 4 个股价 (包括今天的股价 75) 都小于或等于今天的股价。
stockSpanner.next(85);  // 返回 6

思路:

class StockSpanner:def __init__(self):self.prices = []self.stack = deque()def next(self, price: int) -> int:n = len(self.prices)while self.stack and self.prices[self.stack[-1]] <= price:self.stack.pop()if self.stack:ans = n - self.stack[-1]else:ans = n + 1self.stack.append(n)self.prices.append(price)return ans
42. 接雨水

问题描述:

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

Example:

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

思路一:暴力法,按照列遍历,然后寻找当前位置 i i i两侧最大的列 l l l r r r,当前列可以接的雨水体积就是 m i n ( h e i g h t s [ l ] , h e i g h t s [ r ] ) − h e i g h t s [ i ] min(heights[l],heights[r]) - heights[i] min(heights[l],heights[r])heights[i],对所有列的雨水(除了第一列和最后一列)求和就是最终的结果

class Solution:def trap(self, height: List[int]) -> int:n = len(height)ans = 0 for i in range(n):if i == 0 or i == n - 1:continueh = self.exapnd(height, i)if h > height[i]:ans += h - height[i]return ansdef exapnd(self, height, pos):left_max = right_max = height[pos]for i in range(pos - 1, -1, -1):if height[i] > left_max:left_max = height[i]for i in range(pos + 1, len(height)):if height[i] > right_max:right_max = height[i]return min(left_max, right_max)

优化,如果再求每一列的雨水时都遍历搜索两侧最高的列,那么总的时间复杂度是 O ( n 2 ) O(n^2) O(n2),但实际上我们可以预先求出每个位置两侧最高的列,这样求每一列的雨水时就无需再遍历搜索,总的时间复杂度是 O ( n ) O(n) O(n)

class Solution:def trap(self, height: List[int]) -> int:n = len(height)left_max = [0] * nright_max = [0] * nleft_max[0] = height[0]for i in range(1, n):left_max[i] = max(left_max[i - 1], height[i])right_max[n - 1] = height[-1]for i in range(n - 2, -1, -1):right_max[i] = max(right_max[i + 1], height[i])ans = 0 for i in range(n):if i == 0 or i == n - 1:continueh = min(left_max[i], right_max[i])if h > height[i]:ans += h - height[i]return ans

思路二:单调栈,除了按照列遍历,我们还可以按照行计算,如下图所示,按照这种思路,我们本质上是在寻找凹形结构,也就是寻找当前位置左侧和右侧第一个高于当前位置的列,对于这类问题很容易联想到单调栈

如果栈为空或者栈顶元素的高度大于当前位置的高度,直接入栈,否则弹出栈顶元素 t o p top top,如果此时栈不为空,说明形成了一个凹槽,可以接雨水,接下来就是计算凹槽中矩形的面积。矩形的长度为当前位置和此时栈顶元素所处位置中间区域的长度,即 i − s t a c k [ − 1 ] − 1 i - stack[-1] - 1 istack[1]1,高度为 m i n ( h e i g h t [ i ] , h e i g h t [ s t a c k [ − 1 ] ] ) − h e i g h t [ t o p ] min(height[i], height[stack[-1]])-height[top] min(height[i],height[stack[1]])height[top] ,在得到了长度和高度后就可以计算矩形面积并累加到结果中

class Solution:def trap(self, height: List[int]) -> int:n = len(height)stack = deque()ans = 0for i in range(n):while stack and height[stack[-1]] < height[i]:top = stack.pop()if stack:w = i - stack[-1] - 1h = min(height[i], height[stack[-1]]) - height[top]area = w * hans += areastack.append(i)return ans
84. 柱状图中最大的矩形

问题描述:

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

Example:

输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

思路一:暴力法,这道题初看很复杂,但实际上和接雨水类似,我们可以按照列来遍历,以当前列的柱子高度为矩形高度向两侧扩展,如果两侧的柱子更高,那么扩展不会受影响,但如果遇到更低的柱子,就无法继续扩展下去,此时形成的矩形就是以当前列的柱子高度为矩形高度所能达到的最大矩形,如果对每一列都进行扩展,那么我们就可以求出在该柱状图中,能够勾勒出来的矩形的最大面积

因此,该问题就转换成了寻找当前位置 i i i左侧和右侧第一个低于当前位置的列 l l l r r r,从而以当前列的柱子高度为矩形高度扩展形成的最大矩形面积就可以表示为 ( r − l − 1 ) × h e i g h t s [ i ] (r - l - 1) \times heights[i] (rl1)×heights[i]

class Solution:def largestRectangleArea(self, heights: List[int]) -> int:n = len(heights)min_left = [0] * nmin_right = [0] * nmin_left[0] = -1for i in range(1, n):t = i - 1while t >= 0 and heights[t] >= heights[i]:t = min_left[t]min_left[i] = tmin_right[n - 1] = nfor i in range(n - 2, -1, -1):t = i + 1while t < n and heights[t] >= heights[i]:t = min_right[t]min_right[i] = tans = 0for i in range(n):left = min_left[i]right = min_right[i]area = (right - left - 1) * heights[i]ans = max(ans, area)        return ans

优化,和接雨水同理,我们可以预先求出每个位置两侧第一个低于当前位置的列,这里我们用 l e f t [ i ] left[i] left[i] r i g h t [ i ] right[i] right[i]来分别表示 i i i左侧第一个小于 h e i g h t s [ i ] heights[i] heights[i]的柱子下标和 i i i右侧第一个小于 h e i g h t s [ i ] heights[i] heights[i]的柱子下标,对于位置 i i i左侧第一个小于当前位置的下标求解,我们可以从 k = i − 1 k=i-1 k=i1的位置开始往前搜索,如果 k ≥ 0 k \ge0 k0 h e i g h t s [ k ] ≥ h e i g h t s [ i ] heights[k] \ge heights[i] heights[k]heights[i],说明位置 k k k的高度仍然不小于当前高度,此时我们从左侧第一个小于 h e i g h t s [ k ] heights[k] heights[k]的位置继续寻找,即 k = l e f t [ k ] k=left[k] k=left[k],如此循环直到 h e i g h t s [ k ] < h e i g h t s [ i ] heights[k] < heights[i] heights[k]<heights[i]或者 k < 0 k < 0 k<0,对于第一种情况,说明我们找到了第一个小于当前位置的柱子,而第二种情况说明不存在更小的柱子,这里我们将 l e f t left left数组全部初始化为-1,这样 l e f t [ i ] = − 1 left[i]=-1 left[i]=1就代表左侧没有更小的柱子, r i g h t right right数组的求解也是同理

class Solution:def largestRectangleArea(self, heights: List[int]) -> int:n = len(heights)left = [-1] * nright = [n] * nfor i in range(1, n):k = i - 1while k >= 0 and heights[k] >= heights[i]:k = left[k]left[i] = kfor i in range(n - 2, -1, -1):k = i + 1while k <= n - 1 and heights[k] >= heights[i]:k = right[k]right[i] = kans = 0for i in range(n):ans = max(ans, (right[i] - left[i] - 1) * heights[i])return ans

思路二:单调栈,这里和接雨水是一样的思路,只是维护栈内的单调递减顺序变成了维护栈内的单调递增顺序,但有一点不同的是,我们预先在 h e i g h t s heights heights的两侧各插入了一个0,这是为什么呢?

在接雨水中,第一个和最后一个位置是接不到雨水的,所以不需要对左右边界做额外的处理,但是在本题中所有列都需要求其所能扩展成的最大矩形面积,而单调栈中只有栈内有元素且违反了递增顺序时才会进行处理,这就导致代码可能无法考虑左右边界的情况,插入0就可以保证左右边界的柱子不为0时一定会进入while循环中,从而保证不遗漏边界的情况

class Solution:def largestRectangleArea(self, heights: List[int]) -> int:heights.append(0)heights.insert(0, 0)n = len(heights)stack = deque()ans = 0for i in range(n):while stack and heights[stack[-1]] > heights[i]:top = stack.pop()if stack:w = i - stack[-1] - 1h = heights[top]area = w * hans = max(ans, area)stack.append(i)return ans

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/852.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MongoDB如何使用

1.简单介绍 MongoDB是一个开源、高性能、无模式的文档型数据库&#xff0c;当初的设计就是用于简化开发和方便扩展&#xff0c;是NoSQL数据库产品中的一种。是最 像关系型数据库&#xff08;MySQL&#xff09;的非关系型数据库。 MongoDB是一个基于分布式文件存储的数据库由C语…

二、BIO、NIO编程与直接内存、零拷贝

一、网络通信 1、什么是socket&#xff1f; Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层&#xff0c;它是一组接口&#xff0c;一般由操作 系统提供。客户端连接上一个服务端&#xff0c;就会在客户端中产生一个 socket 接口实例&#xff0c;服务端每接受 一个客户端…

git flow流程拆解实践指导

常听人说到git flow,但实际开发过程中是如何落地的? 现在让我们按实际工作中的步骤进行拆解,大家完全可以不用通读,当遇到相应流程步骤时能用上本说明进行查阅参考即可,希望对于推进git flow流程的实际落地起到一些积极的作用. 目录 正常版本开发 开始一个特性开发提测一个版…

Ollama私有化部署大语言模型LLM

目录 一、Ollama介绍 二、安装Ollama 1、标准安装 2、国内加速 三、升级Ollama版本 四、使用Ollama 1、启动ollama服务 systemctl start ollama.service ollama serve 2、使用ollama命令 ollama run 运行模型 ollama ps 查看正在运行的模型 ollama list 查看(本地)…

Matlab一些使用技巧

代码分段 两个百分号就可以实现代码的分段&#xff0c;不同段之间会以不同的背景色显示&#xff0c;方便调试 如下&#xff1a; %% 腐蚀 stlen TimeWidth*Fs/50; %线性算子的长度&#xff0c;1/100的脉宽&#xff0c;对应0.5us&#xff0c;15个采样点 stlen 100; SE strel…

改进萤火虫算法之七:基于自适应机制的萤火虫算法(Adaptive Firefly Algorithm, AFA)

基于自适应机制的萤火虫算法(Adaptive Firefly Algorithm, AFA)是一种结合了萤火虫算法与自适应调整机制的优化算法。 一、基本原理 萤火虫算法是一种基于群体智能的优化算法,其灵感来源于自然界中萤火虫通过闪光进行信息交互和相互吸引的行为。而基于自适应机制的萤火虫算法…

RabbitMQ基础(简单易懂)

RabbitMQ高级篇请看&#xff1a; RabbitMQ高级篇-CSDN博客 目录 什么是RabbitMQ&#xff1f; MQ 的核心概念 1. RabbitMQ 的核心组件 2. Exchange 的类型 3. 数据流向说明 如何安装RabbitQueue&#xff1f; WorkQueue&#xff08;工作队列&#xff09;&#xff1a; Fa…

VScode python 远程调试

https://zhuanlan.zhihu.com/p/564709397 VScode python 远程调试 launch.json 改变conda环境&#xff0c;直接在右下角选择

RuoYi Cloud项目解读【四、项目配置与启动】

四、项目配置与启动 当上面环境全部准备好之后&#xff0c;接下来就是项目配置。需要将项目相关配置修改成当前相关环境。 1 后端配置 1.1 数据库 创建数据库ry-cloud并导入数据脚本ry_2024xxxx.sql&#xff08;必须&#xff09;&#xff0c;quartz.sql&#xff08;可选&…

【深度学习】布匹寻边:抓边误差小于3px【附完整链接】

布匹寻边 项目简介 布匹寻边是指布料裁剪过程中&#xff0c;通过AI寻边技术自动识别布匹的边缘&#xff0c;将检测到的边缘信息输出&#xff0c;确保裁剪的准确性&#xff0c;减少浪费&#xff0c;并提高生产效率。 项目需求 将打满针眼的布匹边缘裁剪掉&#xff0c;且误差小…

LKT4304新一代算法移植加密芯片,守护物联网设备和云服务安全

凌科芯安作为一家在加密芯片领域深耕18年的企业&#xff0c;主推的LKT4304系列加密芯片集成了身份认证、算法下载、数据保护和完整性校验等多方面安全防护功能&#xff0c;可以为客户的产品提供一站式解决方案&#xff0c;并且在调试和使用过程提供全程技术支持&#xff0c;针对…

晨辉面试抽签和评分管理系统之六:面试答题倒计时

晨辉面试抽签和评分管理系统&#xff08;下载地址:www.chenhuisoft.cn&#xff09;是公务员招录面试、教师资格考试面试、企业招录面试等各类面试通用的考生编排、考生入场抽签、候考室倒计时管理、面试考官抽签、面试评分记录和成绩核算的面试全流程信息化管理软件。提供了考生…

2025封禁指定国家ip-安装xtables-addons记录

如何安装和使用 安装lux仓库(该仓库包含xtables-addons所需的依赖环境) # wget http://repo.iotti.biz/CentOS/7/noarch/lux-release-7-1.noarch.rpm # rpm -ivh lux-release-7-1.noarch.rpm 安装xtables-addons。注意&#xff1a;必须先安装kmod-xtables-addons&#xff0c;再…

使用RSyslog将Nginx Access Log写入Kafka

个人博客地址&#xff1a;使用RSyslog将Nginx Access Log写入Kafka | 一张假钞的真实世界 环境说明 CentOS Linux release 7.3.1611kafka_2.12-0.10.2.2nginx/1.12.2rsyslog-8.24.0-34.el7.x86_64.rpm 创建测试Topic $ ./kafka-topics.sh --zookeeper 192.168.72.25:2181/k…

[离线数仓] 总结二、Hive数仓分层开发

接 [离线数仓] 总结一、数据采集 5.8 数仓开发之ODS层 ODS层的设计要点如下: (1)ODS层的表结构设计依托于从业务系统同步过来的数据结构。 (2)ODS层要保存全部历史数据,故其压缩格式应选择压缩比率,较高的,此处选择gzip。 CompressedStorage - Apache Hive - Apac…

MySQL的小问题

编码问题 不管官方使用什么编码&#xff1a;latin1、gbk、utf8、utfmb4。统一使用utfmb4 MySQL中的utf8并不是utf-8&#xff0c;它省略了一个字节&#xff0c;只是用三个字节存储所有的符号&#xff0c;utfmb4才是utf-8 远程登录问题&#xff1a; MySQL官方默认没有启动远程…

一些计算机零碎知识随写(25年1月)-1

我原以为世界上有技术的那批人不会那么闲&#xff0c;我错了&#xff0c;被脚本真实了。 今天正隔着画画呢&#xff0c;手机突然弹出几条安全告警通知。 急忙打开服务器&#xff0c;发现问题不简单&#xff0c;直接关服务器重装系统..... 首先&#xff0c;不要认为小网站&…

golang OpcUaClient

实现功能 package mainimport ("fmt""log""opcuaclient/util/plugin/client/opcclient""os""os/signal""syscall" )func main() {OPCUATest()// 监听操作系统信号&#xff0c;阻塞直到接收到信号quit : make(chan…

【微服务】面试 1、概述和服务发现

微服务面试题 课程内容架构 Spring Cloud 部分 服务注册&#xff1a;重点讲解&#xff08;Nacos&#xff09;和&#xff08;Eureka&#xff09;&#xff0c;这是微服务架构中实现服务发现与注册的关键组件&#xff0c;确保服务间能够相互定位与通信。负载均衡&#xff1a;涵盖…

Elasticsearch学习(1) : 简介、索引库操作、文档操作、RestAPI、RestClient操作

目录 1.elasticsearch简介1.1.了解es1.2.倒排索引正向索引和倒排索引 1.3.es的一些概念:文档和字段&#xff1b;索引和映射&#xff1b;Mysql与ES1.4.安装es、kibana部署单点es部署kibanaIK分词器安装IK分词器与测试扩展与停用词词典总结 部署es集群 2.索引库操作2.1.mapping映…