第五章 栈与队列 part02
今日内容:
- 20.有效的括号
- 1047.删除字符串中的所有相邻重复项
- 150.逆波兰表达式求值
详细布置
20.有效的括号
讲完了栈实现队列,队列实现栈,接下来就是栈的经典应用了。
大家先自己思考一下 有哪些不匹配的场景,在看视频 我讲的都有哪些场景,落实到代码其实就容易很多了。
题目链接/文章讲解/视频讲解:https://programmercarl.com/0020.%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.html
c++:
思路:从前往后开始模拟,如果是左括号的话,加入栈中,如果是右括号的话,则与当前的栈
看下是否匹配(根据栈的特性先进后出),所以当前栈里的元素一定是距离当前右括号最近的
那个左括号,然后依次匹配即可
class Solution {
public:
bool isValid(string s) {
stack<int> stk;
for(auto c:s){
if(c == '(' || c == '{' || c == '[')stk.push(c);
else {
if(stk.empty())return false;
if(c == ')' && stk.top() == '(') stk.pop();
else if(c == '}' && stk.top() == '{') stk.pop();
else if(c == ']' && stk.top() == '[') stk.pop();
else return false;
}
}
return stk.empty();
}
};
java:
解题思路:
- 算法原理
- 栈先入后出特点恰好与本题括号排序特点一致,即若遇到左括号入栈,遇到右括号时将对应栈顶左括号出栈,则遍历完所有括号后
stack
仍然为空; - 建立哈希表
dic
构建左右括号对应关系:key 左括号,value 右括号;这样查询 2个括号是否对应只需 O(1) 时间复杂度;建立栈stack
,遍历字符串s
并按照算法流程一一判断。
- 栈先入后出特点恰好与本题括号排序特点一致,即若遇到左括号入栈,遇到右括号时将对应栈顶左括号出栈,则遍历完所有括号后
- 算法流程
- 如果
c
是左括号,则入栈 push; - 否则通过哈希表判断括号对应关系,若
stack
栈顶出栈括号stack.pop()
与当前遍历括号c
不对应,则提前返回 false。
- 如果
- 提前返回 false
- 提前返回优点: 在迭代过程中,提前发现不符合的括号并且返回,提升算法效率。
- 解决边界问题:
- 栈
stack
为空: 此时stack.pop()
操作会报错;因此,我们采用一个取巧方法,给stack
赋初值 ?,并在哈希表dic
中建立 key:′?′,value:′?′的对应关系予以配合。此时当stack
为空且c
为右括号时,可以正常提前返回 false; - 字符串
s
以左括号结尾: 此情况下可以正常遍历完整个s
,但stack
中遗留未出栈的左括号;因此,最后需返回len(stack) == 1
,以判断是否是有效的括号组合。
- 栈
- 复杂度分析
- 时间复杂度 O(N):正确的括号组合需要遍历 1遍
s
; - 空间复杂度 O(N):哈希表和栈使用线性的空间大小。
- 时间复杂度 O(N):正确的括号组合需要遍历 1遍
class Solution {
private static final Map<Character,Character> map = new HashMap<Character,Character>(){
{
put('{','}');
put('[',']');
put('(',')');
put('?', '?');
}
};
public boolean isValid(String s) {
if(s.length() > 0 && !map.containsKey(s.charAt(0))) return false;
LinkedList<Character> stack = new LinkedList<Character>() { {add('?');}};
for(Character c : s.toCharArray()){
if(map.containsKey(c)) stack.add(c);
else if(map.get(stack.removeLast()) != c || stack.isEmpty()) return false;
}
return stack.size() == 1;
}
}
1047.删除字符串中的所有相邻重复项
栈的经典应用。
要知道栈为什么适合做这种类似于爱消除的操作,因为栈帮助我们记录了 遍历数组当前元素时候,前一个元素是什么。
题目链接/文章讲解/视频讲解:https://programmercarl.com/1047.%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.html
c++:
(栈) O(n)
- 开一个栈,然后扫描整个字符串。
- 如果当前字符和栈顶元素不相等,则当前字符入栈;如果相等,则当前栈顶元素出栈。
- 最后将栈中元素全部出栈,按正序拼成答案即可。
时间复杂度
- 由于每个字符最多被扫描一次,故时间复杂度为 O(n)。
空间复杂度
-
需要额外的栈空间,以及答案还需要空间存储,故总空间复杂度为 O(n)。
本题拿字符串直接作为栈,这样省去了栈还要转为字符串的操作。
class Solution {
public:
string removeDuplicates(string s) {
string res;
for(char c : s)
{
if(res.size() > 0 && c == res.back()) res.pop_back();
else res += c;
}
return res;
}
};
java:
class Solution {
public String removeDuplicates(String s) {
char[] cs = s.toCharArray();
Deque<Character> d = new ArrayDeque<>();
for (char c : cs) {
if (!d.isEmpty() && d.peekLast().equals(c)) {
d.pollLast();
} else {
d.addLast(c);
}
}
StringBuilder sb = new StringBuilder();
while (!d.isEmpty()) sb.append(d.pollLast());
sb.reverse();
return sb.toString();
}
}
150.逆波兰表达式求值
本题不难,但第一次做的话,会很难想到,所以先看视频,了解思路再去做题
题目链接/文章讲解/视频讲解:https://programmercarl.com/0150.%E9%80%86%E6%B3%A2%E5%85%B0%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B1%82%E5%80%BC.html
c++:
(栈操作) O(n)
遍历所有元素。如果当前元素是整数,则压入栈;如果是运算符,则将栈顶两个元素弹出做相应运算,再将结果入栈。
最终表达式扫描完后,栈里的数就是结果。
时间复杂度分析:每个元素仅被遍历一次,且每次遍历时仅涉及常数次操作,所以时间复杂度是 O(n)。
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<long long> st;
for(auto c: tokens){
if(c == "+" || c == "-" || c == "*" || c == "/"){
long long b = st.top(); st.pop(); // 后面一个是第二个操作数 (要注意哦)
long long a = st.top(); st.pop();
long long x;
if(c == "+") x = a + b;
else if(c == "-") x = a - b;
else if(c == "*") x = a * b;
else x = a / b;
st.push(x);
}else st.push(stoi(c)); // 是一个数的话直接 st.push(c)
} // stoi(c) 把字符串类型的数字变成, int 类型的数字
return st.top();
}
};
java:
/**
* 评估逆波兰表达式。
*
* @param tokens 代表逆波兰表达式的字符串数组,其中每个元素要么是整数,要么是四则运算符(+、-、*、/)
* @return 逆波兰表达式的结果
*/
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for(String s : tokens){
// 遇到运算符时,执行相应的计算操作
if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/"))
{
int b = stack.pop();
int a = stack.pop();
if(s.equals("+")) stack.push(a + b);
else if (s.equals("-")) stack.push(a - b);
else if (s.equals("*")) stack.push(a * b);
else stack.push(a / b);
}else{
// 遇到数字时,将其转换为整数并入栈
stack.push(Integer.valueOf(s));
}
}
// 最后栈中剩余的数字即为表达式的计算结果
return stack.pop();
}