接下来,我们用栈来解决几个问题。
- 括号配对
leetcode原题:
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()" 输出:true
示例 2:
输入:s = "()[]{}" 输出:true
示例 3:
输入:s = "(]" 输出:false
提示:
1 <= s.length <= 104
s
仅由括号'()[]{}'
组成
解题思路:
- 遇到左括号就把与它对应的右括号压入栈
- 遇到右括号就从栈里面弹出栈顶与之比较,一致则继续,不一致就直接return false
@Test
@DisplayName("括号配对算法")
public void test37()
{
//有效括号
System.out.println(isValid("()"));
System.out.println(isValid("()[]{}"));
System.out.println(isValid("{[]}"));
System.out.println("------------");
//无效括号
System.out.println(isValid("(]"));
System.out.println(isValid("([)]"));
System.out.println(isValid("]"));
System.out.println("-----------------");
System.out.println(isValid(")("));
System.out.println(isValid("]"));
}
/**
* 括号配对算法
* @param s 字符串
* @return 是否有效
*/
public boolean isValid(String s)
{
ArrayStack<Character> stack = new ArrayStack<>(s.length());//创建一个容量为字符串长度的栈
//遍历字符串
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {//如果是左括号,就把对应的右括号入栈
case '(':
stack.push(')');
break;
case '[':
stack.push(']');
break;
case '{':
stack.push('}');
break;
default://如果是右括号,就把栈顶元素出栈,然后判断是否相等
if (!stack.isEmpty() && c == stack.peek())
{
stack.pop();
}
else
{
return false;
}
}
}
if (stack.isEmpty())//如果栈为空,就说明括号全部配对成功
{
return true;
}
else
{
return false;
}
}
- 逆波兰表达式
逆波兰表达式(Reverse Polish Notation,RPN),也称为后缀表达式,是一种数学和计算机科学中的表达式表示方法。在逆波兰表达式中,操作符位于操作数的后面,这种表示方法可以消除括号,使运算顺序更加清晰。
在逆波兰表达式中,每个运算符都跟随着其操作数,使得解析表达式变得更加直观。这种表示法的一个重要特点是,操作符总是作用于最近的操作数,避免了操作符优先级和括号的问题。
例如,中缀表达式 "3 + 4" 在逆波兰表达式中表示为 "3 4 +"。更复杂的表达式也可以用逆波兰表达式表示,如 "(5 + 2) * 3" 可以表示为 "5 2 + 3 *"。
逆波兰表达式在计算器、编程语言的解释器以及某些计算机科学应用中被广泛使用,因为它提供了一种直观且不需要解析括号的方式来表示和计算算术表达式。
解题思路:
- 遇到数字就把数字压入栈,遇到运算符就弹出两个数字进行相应运算
@Test
@DisplayName("后缀表达式")
public void test38()
{
String[] suffixExpression = {"2","1","+","3","*"};
System.out.println(calculate(suffixExpression));
String[] suffixExpression2 = {"4","13","5","/","+"};
System.out.println(calculate(suffixExpression2));
}
/**
* 后缀表达式
* @param tokens 后缀表达式
* @return 计算结果
*/
public int calculate(String[] tokens)
{
LinkedList<Integer> stack = new LinkedList<>();
for (String t : tokens)//遍历字符数组
{
switch (t){
case "+" :
stack.push(stack.pop() + stack.pop());
break;
case "-" :
stack.push(-stack.pop() + stack.pop());//注意顺序,后入先出
break;
case "*" :
stack.push(stack.pop() * stack.pop());
break;
case "/" :
int b= stack.pop();
int a= stack.pop();
stack.push(a / b);
break;
default:
stack.push(Integer.parseInt(t));
break;
}
}
return stack.pop();//返回栈顶元素
}
- 中缀转后缀
众所周知,java最后会被jvm转成汇编代码,而汇编语言中使用的就是后缀表达式
那么用Java怎么把中缀转成后缀表达式形式呢?
解题思路:
- 遇到数字字符就直接拼接到StringBuilder中去
- 遇到运算符就先判断其优先级
- 如果栈为空或当前运算符高于栈顶的运算符,就直接入栈
- 若当前运算符低于或等于栈顶运算符,就循环弹出栈顶元素,并拼接到StringBuilder
- 当遍历完输入的字符串后,循环弹出栈顶元素,并拼接到StringBuilder
- 遇到括号这种辅助原算符时为了不扰乱以上流程,要做特殊处理:
- 1.将左括号的优先级定义为最低
- 2.遇到左括号直接入栈
- 3遇到右括号弹出左括号前面的使用栈顶元素,拼接到StringBuilder,然后弹出左括号
@Test
@DisplayName("中缀表达式转后缀表达式")
public void test39()
{
String expression = "1+((2+3)*4)-5";//123+4*+5-
System.out.println(infixToSuffix(expression));
expression = "1+2*3";
System.out.println(infixToSuffix(expression));
}
/**
* 运算符优先级
* @param c 运算符
* @return 优先级
*/
private int priority(char c)
{
switch (c) {
case '(':
return 0;
case '+','-':
return 1;
case '*', '/':
return 2;
default:
throw new IllegalArgumentException("非法运算符"+c);
}
}
/**
* 中缀表达式转后缀表达式
* @param expression 中缀表达式
* @return 后缀表达式
*/
public String infixToSuffix(String expression)
{
LinkedList<Character> stack = new LinkedList<>();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < expression.length(); i++)
{
char c = expression.charAt(i);
switch (c){
case '+','-','*','/' ://这是增强型的case写法,要java14以上才支持
if (stack.isEmpty())//如果栈为空,就把运算符入栈
{
stack.push(c);
}
else
{
if (priority(c) > priority(stack.peek()))//如果栈顶运算符的优先级小于当前运算符的优先级,就把当前运算符入栈
{
stack.push(c);
}
else
{
while (!stack.isEmpty() && priority(stack.peek()) >= priority(c))//如果栈顶运算符的优先级大于等于当前运算符的优先级,就把栈顶运算符出栈并拼接
{
sb.append(stack.pop());
}
stack.push(c);
}
}
break;
case '(' ://如果是左括号,就入栈
stack.push(c);
break;
case ')' ://如果是右括号,就把栈顶运算符出栈并拼接,直到遇到左括号
while (!stack.isEmpty() && stack.peek() != '(')
{
sb.append(stack.pop());
}
stack.pop();
break;
default://如果是数字,就直接拼接
sb.append(c);
break;
}
}
while (!stack.isEmpty())//字符串遍历完后如果栈不为空,就把栈顶运算符出栈并拼接
{
sb.append(stack.pop());
}
return sb.toString();
}