栈的经典应用
括号匹配是使用栈解决的经典问题。
栈解决括号匹配:相邻的括号匹配了,则做相应的消除,匹配的动作
写代码之前要分析好有哪几种不匹配的情况
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配
但还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
Q&A:
Q:什么叫以正确的顺序闭合?[ { } ( ] ) 比如如果是这样就过不了,[ { } ( ) ]这样才能过吗?
A:是的。大概指的就是先遍历到的后闭合,后遍历到的先闭合吧。先这样吧,理解代码就行,不要抠字眼。
左括号对应右边括号,一定要左右都互相对应(){}[]
代码随想录算法训练营第十一天| 20. 有效的括号 、1047. 删除字符串中的所有相邻重复项 、150. 逆波兰表达式求值 - 知乎
import java.util.LinkedList;
public class ValidParentheses {
public boolean isValid(String s){
//字母character
LinkedList<Character> stack = new LinkedList<Character>();
for (char c : s.toCharArray()){
//正常都匹配的情况:遇到左括号,把对应右括号入栈
if(c == '('){
stack.push(')');
}else if(c == '['){
stack.push(']');
}else if(c == '{') {
stack.push('}');
//不匹配情况:1。遇到右括号,发现与栈顶元素不匹配stack.pop()!=c
//不匹配情况:2。遇到右括号,此时栈为空,无法匹配stack.isEmpty()
//tips:以上两个情况不能反过来,否则要报空指针
}else if(stack.isEmpty() || stack.pop()!=c){
return false;
}
}
//遍历完之后,栈不为空,直接return false
//正常都匹配的情况下,遍历完栈为空
//所以,字符串遍历完之后,判断栈是否为空,就是判断正常匹配还是非正常匹配
return stack.isEmpty();
}
}
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new LinkedList<>();
char ch;
for(int i = 0; i < s.length(); i++){
ch = s.charAt(i);
if(ch == '('){
deque.push(')');
}else if(ch == '{'){
deque.push('}');
}else if(ch == '['){
deque.push(']');
//栈为空或者栈顶元素不等于当前遍历的字符串
}else if(deque.isEmpty()|| deque.peek() != ch){
return false;
}else{
//第一种,遍历的字符串等于栈顶元素
deque.pop();
}
}
//第一种,遍历完字符串一定为空,如果不为空就是return false
return deque.isEmpty();
}
}
栈的经典应用。
本题要删除相邻相同元素,相对于20. 有效的括号 (opens new window)来说其实也是匹配问题,20. 有效的括号 是匹配左右括号,本题是匹配相邻元素,最后都是做消除的操作
相邻的字母相同,做消除的动作
要知道栈为什么适合做这种类似于爱消除的操作,因为栈帮助我们记录了 遍历数组当前元素时候,前一个元素是什么
StringBuilder 对象类似于 String 对象,但可以修改它们。
import java.util.LinkedList; public class RemoveAllAdjacentDuplicatesInString { public String removeDuplicates(String s){ LinkedList<Character> stack = new LinkedList<>(); for(char c : s.toCharArray()){ if(stack.isEmpty() || stack.peek() != c ){ stack.push(c); continue; }else { //与栈中元素相等,则栈里元素弹出 stack.pop(); } } StringBuilder sb = new StringBuilder(); for(char c: stack){ sb.append(c); } sb.reverse();//把栈里元素翻转 return sb.toString(); } }
class Solution {
public String removeDuplicates(String s) {
//字符串模拟栈
//拿字符串直接作为栈,省去了栈还要转为字符串的操作
//使用deque作为堆栈
//ArrayDeque会比LinkedList在除了删除元素这一点外会快一点
//参考:https://stackoverflow.com/questions/6163166/why-is-arraydeque-better-than-linkedlist
ArrayDeque<Character> deque = new ArrayDeque<>();
char ch;
for(int i = 0; i < s.length(); i++){
ch = s.charAt(i);
if(deque.isEmpty() ||deque.peek()!=ch){
deque.push(ch);
}else{
deque.pop();
}
}
String str = "";
//剩余的元素即为不重复的元素
while(!deque.isEmpty()){
str = deque.pop()+ str;
}
return str;
}
}
// Deque 接口继承了 Queue 接口
// 所以 Queue 中的 add、poll、peek等效于 Deque 中的 addLast、pollFirst、peekFirst
import java.util.Stack; public class EvaluateReversePolishNotation { public int evalRPN(String[] tokens){ Stack<Integer> stack = new Stack<>(); for(String str : tokens){ if(!"+-*/".contains(str)){//如果是数字就入栈 int num = Integer.parseInt(str);//string数字转int数字 stack.push(num); }else { //符号就把元素(数字)弹出来,stack是后进先出 int num1 = stack.pop(); int num2 = stack.pop(); if(str.equals("+")){ stack.push(num1 + num2); }else if(str.equals("-")){ stack.push(num2 - num1); }else if(str.equals("*")) { stack.push(num1 * num2); }else { stack.push(num2 / num1); } } } return stack.pop(); } }