代码随想录算法训练营第十一天 | 20. 有效的括号、1047. 删除字符串中的所有相邻重复项、150. 逆波兰表达式求值
今日学习的文章链接和视频链接
参考代码随想录
自己看到题目的第一想法
- 20. 有效的括号和1047. 删除字符串中的所有相邻重复项之前刷过,应该可以不看笔记写出来
- 150. 逆波兰表达式求值和之前刷过的227.基本计算器II思路很像
自己实现过程中遇到哪些困难
- 20. 有效的括号秒了
- 1047. 删除字符串中的所有相邻重复项没有先判断栈为空的情况
- 150. 逆波兰表达式求值看了提示做出来,忘记了Java中字符串的相等判断要用
.equals
今日收获,记录一下自己的学习时长
- 应打卡7月8日,7月10日补打卡,学习时长4hr
- 重点复习了Python中用list实现栈相关的几个接口
- 学习了Java中用deque实现栈的两种方式,分别是
Deque<Integer> stack = new ArrayDeque<>()
和Deque<Integer> stack = new LinkedList<>()
,也学习了deque实现栈的相关接口,包括push()
,pop()
,peek()
,isEmpty()
。
0020. 有效的括号 Valid Parentheses
1. 题目描述
给定一个只包括'('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
2. 解题思路
- 本题是使用栈的经典题目。由于栈的结构特性,栈很适合匹配类问题。
- 括号不匹配的情况有:
- 左括号多余(
'(('
) - 括号没有多余,但是左括号和右括号没有匹配上(
'(]'
) - 右括号多余(
'))'
)
- 左括号多余(
- 基本思路为遍历字符串
s
,遍历到左括号就将对应的右括号入栈,遍历到右括号就与当前栈顶元素相比较并出栈;如果最后栈不为空,就说明没有匹配
具体算法:
- 判断字符串
s
的长度,如果长度为奇数,一定不匹配,返回False
- 定义栈
record
用来存储匹配的右括号 - 遍历字符串
- 如果遍历到左括号(
'('
,'['
,'{'
),将对应的右括号(')'
,']'
,'}'
)入栈 - 如果遍历到右括号,先判断栈是否为空,如果栈为空说明右括号多余,返回
False
;然后将右括号与当前栈顶元素相对比,如果相同就讲栈顶元素出栈,继续遍历;否则说明不匹配,返回False
- 如果遍历到左括号(
- 遍历结束后,判断栈是否为空;如果为空说明匹配;如果不为空,说明左括号多余,返回
False
复杂度分析:
- 时间复杂度:
O(n)
- 空间复杂度:
O(n)
3. 算法实现
3.1 Python - 数组做栈
- Python中用数组做栈,注意栈的接口,栈顶入栈用
append()
,栈顶出栈用pop()
(不添加参数时默认参数为-1
)
class Solution:
def isValid(self, s: str) -> bool:
# 判断括号数量
if len(s) % 2 != 0:
return False
# 数组做栈
record = []
for ch in s:
# 遍历左括号,匹配的右括号入栈
if ch == '(':
record.append(')')
elif ch == '[':
record.append(']')
elif ch == '{':
record.append('}')
else: # 右括号和栈顶元素相比较
# 右括号多余
if len(record) == 0:
return False
if ch == record[-1]:
record.pop()
else: # 左右括号不匹配
return False
# 左括号多余
if len(record) != 0:
return False
return True
3.1 Java - Deque做栈
- Java中用deque做栈,相关的接口有:初始化栈
Deque<> stack = new LinkedList<>()
,栈顶入栈用Stack.push()
相当于Deque.addFirst()
,栈顶出栈用Stack.pop()
相当于Deque.removeFirst()
,返回栈顶元素用Stack.peek()
相当于Deque.peekFirst()
class Solution {
public boolean isValid(String s) {
// 判断字符串数量
if (s.length() % 2 != 0) {
return false;
}
// deque做stack
Deque<Character> stack = new LinkedList<>();
char ch;
for (int i = 0; i < s.length(); i++) {
ch = s.charAt(i);
// 遍历左括号,对应右括号入栈
if (ch == '(') {
stack.push(')');
} else if (ch == '[') {
stack.push(']');
} else if (ch == '{') {
stack.push('}');
} else {
// 右括号多余
if (stack.isEmpty()) {
return false;
}
// 右括号不匹配
if (ch != stack.peek()) {
return false;
} else {
// 右括号匹配,栈顶出栈
stack.pop();
}
}
}
// 左括号多余
if (!stack.isEmpty()) {
return false;
}
return true;
}
}
1047. 删除字符串中的所有相邻重复项 Remove All Adjacent Duplicates in String
1. 题目描述
给出由小写字母组成的字符串S
,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在S
上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例 1:
输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
示例 2:
输入:"azxxzy"
输出:"ay"
2. 解题思路
- 本题要删除相邻的重复项,需要先匹配相邻的元素,思路和0020_有效的括号一样,用栈来解决匹配问题
- 栈的目的是存放遍历过的元素,如果两个相邻的元素是相通的,先把第一个元素入栈,然后遍历到第二个元素时,把该元素和栈元素判断,因为他们相同,栈顶元素出栈;如果两个相邻元素不同,则顺序把元素压入栈顶。
- 注意最后返回字符串时,先依次从栈里出栈,此时字符串顺序是倒序的,需要反转字符串
- 本题也可以使用双指针解决
复杂度分析:
- 时间复杂度:
O(n)
- 空间复杂度:
O(n)
3. 具体算法
3.1 Python - 数组做栈
class Solution:
def removeDuplicates(self, s: str) -> str:
# 数组做栈
res = list()
for char in s:
if len(res) == 0:
res.append(char)
elif char and char == res[-1]:
res.pop()
else:
res.append(char)
# list -> str
return "".join(res)
3.2 Java - ArrayDeque
class Solution {
public String removeDuplicates(String s) {
// Deque as stack
Deque<Character> record = new ArrayDeque<>();
char ch;
for (int i = 0; i < s.length(); i++) {
ch = s.charAt(i);
if (record.isEmpty() || ch != record.peek()) {
record.push(ch);
} else {
record.pop();
}
}
String res = new String();
while (!record.isEmpty()) {
// 栈顶元素依次加在前,相当于反转字符串
res = record.pop() + res;
}
return res;
}
}
0150. 逆波兰表达式求值 Evaluate Reverse Polish Notation
1. 题目描述
给你一个字符串数组tokens
,表示一个根据逆波兰表示法表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
'+'
、'-'
、'*'
和'/'
。 - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是向零截断。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用32位整数表示。
示例 1:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入:tokens = ["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 + *
也可以依据次序计算出正确结果。 - 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
2. 解题思路
- 本题中每一个子表达式得出结果,然后结果再和其他项进行运算,所以基本思路和1047.删除字符串中所有相邻重复项一致
- 逆波兰表达式相当于二叉树中的后续遍历,其中运算符作为中间节点。
具体算法:
- 定义一个栈存储字符串数组
token
中的数字 - 遍历字符串数组,做一下操作:
- 如果遍历到数字,把数字压入栈顶
- 如果遍历到运算符,把栈顶元素和栈顶下一个元素都出栈,两个数字根据运算符做运算,运算结果压入栈顶
- 最后栈里只剩最终的结果,所以返回栈顶元素
复杂度分析:
- 时间复杂度:
O(n)
- 空间复杂度:
O(n)
3. 算法实现
3.1 Python - 数组做栈
- 当运算符为减法
"-"
和除法"/"
时,注意运算顺序,最好能先临时存储第一个栈顶元素(减数 或者 分母),然后存储第二个栈顶元素(被减数 或者 分子)
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
# list as stack
stack = []
for item in tokens:
# 遍历到运算符:压入栈顶
if item == "+":
stack.append(stack.pop() + stack.pop())
elif item == "-":
stack.append(-stack.pop() + stack.pop())
elif item == "*":
stack.append(stack.pop() * stack.pop())
elif item == "/":
second = stack.pop()
first = stack.pop()
stack.append(int(first/second))
else:
stack.append(int(item))
return stack[0]
3.2 Java - deque做栈
- Java中比较数组注意用
String.equals()
- Java中字符
String
转换为整数Integer
用Integer.valueOf()
class Solution {
public int evalRPN(String[] tokens) {
// deque as stack
Deque<Integer> stack = new ArrayDeque<>();
for (String token: tokens) {
if ("+".equals(token)) {
stack.push(stack.pop() + stack.pop());
} else if ("-".equals(token)) {
stack.push(-stack.pop() + stack.pop());
} else if ("*".equals(token)) {
stack.push(stack.pop() * stack.pop());
} else if ("/".equals(token)) {
int temp2 = stack.pop();
int temp1 = stack.pop();
stack.push(temp1 / temp2);
} else {
// 数字入栈
stack.push(Integer.valueOf(token));
}
}
return stack.peek();
}
}