由于栈先进后出的特性,所以很多问题借助栈来处理会变得简单很多。
1.括号匹配问题
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
注意:
1.左括号必须用相同类型的右括号闭合;
2.左括号必须以正确的顺序闭合;
3.注意空字符串可被认为是有效字符串。
思路:借助栈来完成,遍历字符串。遇见左括号便将其入栈,遇见右括号便弹出一个栈顶的元素与其进行匹配,栈为空或者括号不匹配直接返回false。遍历完后,如果栈非空,则说明左括号比右括号多,也返回false。
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if(ch == '(' || ch == '[' || ch == '{') {
stack.push(ch);
}else {
if(stack.empty()) {
return false; //遇见右括号,栈空,返回false
}
char stackTop = stack.peek();
if((ch == ')' && stackTop == '(' )|| (ch == ']' && stackTop == '[') || (ch == '}' && stackTop == '{' ) ) {
stack.pop(); //括号匹配,弹出栈顶元素
}else {
return false; //括号不匹配,返回false
}
}
}
if(!stack.empty()) {
return false; //左括号比右括号多,返回false
}
return true;
}
}
2.逆波兰表达式求值
根据逆波兰表示法,求表达式的值。有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
1.整数除法只保留整数部分;
2.给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
题目链接:逆波兰表达式求值
思路:
关于逆波兰表达式也叫后缀表达式,即将运算符写在操作数之后。
平常我们写的a+b,这种是中缀表达式,写成后缀表达式就是:ab+。再比如:(2 + 1) * 3写成逆波兰表达式就是:2 1 + 3 *;4 + (13 / 5)写成逆波兰表达式就是:4 13 5 / +。
为什么要将看似简单的中序表达式转换为复杂的逆波兰式呢?
原因就在于这个简单是相对人类的思维结构来说的,对计算机而言中序表达式是非常复杂的结构。相对的,逆波兰式在计算机看来却是比较简单易懂的结构。因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。
有了这些了解,思路便明了了:
遇到数字便压入栈中,遇到运算符便取出栈顶两个元素:第一个放在运算符右边,第二个放在左边。然后进行运算,并将结果继续压入栈中。
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
int left,right;
for (int i = 0; i < tokens.length; i++) {
String s = tokens[i];
switch (s) {
//遇到运算符便弹出栈顶两个元素,分别放在运算符右左两侧进行运算
//然后将计算结果重新入栈
case "+":
right = stack.pop();
left = stack.pop();
stack.push(left+right);
break;
case "-":
right = stack.pop();
left = stack.pop();
stack.push(left-right);
break;
case "*":
right = stack.pop();
left = stack.pop();
stack.push(left*right);
break;
case "/":
right = stack.pop();
left = stack.pop();
stack.push(left/right);
break;
default:
//遇到数字便压入栈中
stack.push(Integer.valueOf(s));
break;
}
}
return stack.peek();
}
}
3.用栈将递归转为循环
-
为什么要将递归转化为循环?
因为再递归调用次数非常多的时候,时间开销非常大,而且容易造成栈溢出。使用循环就可以避免这些问题。 -
为什么可以使用栈将递归转化为循环?
因为在使用递归的时候先调用的后结束,后调用的先结束。正好与栈先进后出的特性相匹配。还有就是有些递归直接转化为循环可能很难,这时候就可以转化为栈。
比如逆置打印一个链表:
1.递归法
public static void diaplay(Node head) {
if(head.next == null) {
System.out.println(head.data);
}
diaplay(head.next);
}
2.使用栈转为循环
public static void display(Node head) {
Stack<Node> stack = new Stack<>();
Node cur = head;
while (cur != null) {
stack.push(cur); //进栈
cur = cur.next;
}
while (!stack.empty()) {
Node top = stack.pop(); //出栈
System.out.println(top.data);
}
}