目录
1209. 删除字符串中的所有相邻重复项 II(中等)... 7
剑指 Offer 42. 连续子数组的最大和(简单)... 15
106. 从中序与后序遍历序列构造二叉树(中等)... 96
1120可信上机编程“自动售货机进出货管理系统”... 172
sorted(courses, key=lambda x:x[1]) 179
courses.sort(key=lambda x: x[0]) 179
栈
20. 有效的括号(简单)
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true
示例 2:
输入: "()[]{}"
输出: true
示例 3:
输入: "(]"
输出: false
示例 4:
输入: "([)]"
输出: false
示例 5:
输入: "{[]}"
输出: true
解题思路:栈
算法原理
栈先入后出特点恰好与本题括号排序特点一致,即若遇到左括号入栈,遇到右括号时将对应栈顶左括号出栈,则遍历完所有括号后 stack 仍然为空;
建立哈希表 dic 构建左右括号对应关系:key左括号,value右括号;这样查询 2 个括号是否对应只需 O(1)时间复杂度;建立栈 stack,遍历字符串 s 并按照算法流程一一判断。
算法流程
(1)如果 c 是左括号,则入栈;
(2)如果 c 是右括号,通过哈希表判断括号对应关系,若 stack 栈顶出栈括号 stack.pop() 与当前遍历括号 c 不对应或着栈为空,则提前返回 false。
复杂度分析
时间复杂度 O(N):正确的括号组合需要遍历一遍 s;
空间复杂度 O(N):哈希表和栈使用线性的空间大小。
class Solution:
def isValid(self, s: str) -> bool:
if len(s) % 2 == 1:
return False
pairs = {
')': '(',
']': '[',
'}': '{',
}
stack = list()
for ch in s:
if ch in pairs:
if not stack or stack[-1] != pairs[ch]:
return False
stack.pop()
else:
stack.append(ch)
return not stack
32. 最长有效括号(困难)
给你一个只包含 '('
和 ')'
的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例 2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例 3:
输入:s = ""
输出:0
提示:
0 <= s.length <= 3 * 10
4
s[i]
为'('
或')'
方法:栈
始终保持栈底元素为当前已经遍历过的元素中【最后一个没有被匹配的右括号的下标】,这主要是考虑了边界条件的处理,栈里其它元素维护左括号的下标:
- 对于遇到的每个‘(’,将它的下标放入栈中
- 对于遇到的每个‘)’,先弹出栈顶元素表示匹配了当前右括号:
- 如果栈为空,说明当前的右括号为没有被匹配的右括号,将其下标放入栈中来更新我们之前提到的【最后一个没有被匹配的右括号的下标】
- 如果栈不为空,当前右括号的下标减去栈顶元素即为【以该右括号为结尾的最长有效括号的长度】
class Solution:
def longestValidParentheses(self, s: str) -> int:
if not s:
return 0
res = 0
stack = [-1]
for i in range(len(s)):
if s[i] == '(':
stack.append(i)
else:
stack.pop()
if not stack:
stack.append(i)
else:
res = max(res, i-stack[-1])
return res
1209. 删除字符串中的所有相邻重复项 II(中等)
给你一个字符串 s
,「k
倍重复项删除操作」将会从 s
中选择 k
个相邻且相等的字母,并删除它们,使被删去的字符串的左侧和右侧连在一起。
你需要对 s
重复进行无限次这样的删除操作,直到无法继续为止。
在执行完所有删除操作后,返回最终得到的字符串。
本题答案保证唯一。
示例 1:
输入:s = "abcd", k = 2
输出:"abcd"
解释:没有要删除的内容。
示例 2:
输入:s = "deeedbbcccbdaa", k = 3
输出:"aa"
解释:
先删除 "eee" 和 "ccc",得到 "ddbbbdaa"
再删除 "bbb",得到 "dddaa"
最后删除 "ddd",得到 "aa"
示例 3:
输入:s = "pbbcggttciiippooaais", k = 2
输出:"ps"
提示:
1 <= s.length <= 10^5
2 <= k <= 10^4
s
中只含有小写英文字母。
解题思路:
将元素依次入栈并统计元素数量。每次入栈判断是否和栈顶元素相同:
- 如果与栈顶元素相同,那么将栈顶元素的数量加 1;
- 如果栈顶元素数量达到 3,则将栈顶元素出栈;
- 如果待入栈元素与栈顶元素不同,那么直接入栈并将该元素个数置为 1。
遍历完字符串之后,将栈中剩余元素拼接即为答案。
class Solution:
def removeDuplicates(self, s: str, k: int) -> str:
stack = []
for c in s:
if not stack or stack[-1][0] != c:
stack.append([c, 1])
elif stack[-1][1] + 1 < k:
stack[-1][1] += 1
else:
stack.pop()
# ans = ""
# for c, k in stack:
# ans += c*k
return ("".join(c*k) for c, k in stack)
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//bc/d//././/.."
输出:"/a/b/c"
方法:栈
class Solution:
def simplifyPath(self, path: str) -> str:
stack = []
path = path.split('/')
for item in path:
if item == '..':
if stack:
stack.pop()
elif item and item != '.':
stack.append(item)
return "/" + "/".join(stack)
150. 逆波兰表达式求值(中等)
根据 逆波兰表示法,求表达式的值。
有效的运算符包括 +
, -
, *
, /
。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
输入: ["2", "1", "+", "3", "*"]
输出: 9
解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入: ["4", "13", "5", "/", "+"]
输出: 6
解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
输出: 22
解释:
该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
逆波兰表达式:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
- 平常使用的算式则是一种中缀表达式,如
( 1 + 2 ) * ( 3 + 4 )
。 - 该算式的逆波兰表达式写法为
( ( 1 2 + ) ( 3 4 + ) * )
。
逆波兰表达式主要有以下两个优点:
- 去掉括号后表达式无歧义,上式即便写成
1 2 + 3 4 + *
也可以依据次序计算出正确结果。 - 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
方法:栈
遇到数字则入栈;
遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for i, item in enumerate(tokens):
if item in "+-*/":
numb = stack.pop()
numa = stack.pop()
if item == '+':
stack.append(numa+numb)
elif item == '-':
stack.append(numa-numb)
elif item == '*':
stack.append(numa*numb)
else:
stack.append(int(numa/numb))
else:
stack.append(int(item))
return stack[-1]