有效的括号
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: “()”
输出: true
示例 2:
输入: “()[]{}”
输出: true
示例 3:
输入: “(]”
输出: false
示例 4:
输入: “([)]”
输出: false
示例 5:
输入: “{[]}”
输出: true
总结上面的例子,有三种情况:
1.左边多了括号,如(){}[])
2.右边多了括号,如((){}[]
3.括号不匹配(示例4可以理解成中间为[),所以不匹配)
这道题目可以用栈来完成,当我们遇到左括号时,放入对应的右括号入栈(具体原因请往下看),这样就记住了左括号的顺序。当我们遇到右括号时,如果括号有效,那么出栈的结果应该和对应的右括号一致,就达成了匹配
如:( ( ) ) [ ] { }
开始遍历,第一个为(,放一个)入栈。第二个为( ,放一个)入栈,第三个是右括号,应该和最后一个入栈的)匹配,所以消掉(出栈),第四个也是右括号,发现和即将出栈的)匹配,继续出栈
遍历完成后,如果栈为空说明括号有效。如果遇到了出栈的字符串和目标字符串不匹配(情况3)或空栈遇到右括号(情况2),或遍历完后栈不为空(情况1)即为无效
代码如下:
public static boolean isValid(String s){
Stack<Character> s1 = new Stack<>();
char target;
for (int i = 0; i < s.length(); i++) {
target = s.charAt(i);
if(target == '('){
s1.push(')');
}else if(target == '['){
s1.push(']');
}else if(target == '{'){
s1.push('}');
}else if(s1.isEmpty() || target != s1.peek()){
return false;
}else {
s1.pop();
}
}
return s1.isEmpty();
}
这里要对s1.isEmpty() || target != s1.peek()做一下解释。
有前面几个if和else if做铺垫,能往下走的都是右括号,如果遍历到右括号发现栈已经空了,或者目标和栈不匹配,就直接return false。如果和目标匹配则执行s1.pop出栈。遍历完成后看s1是否为空返回结果
删除字符串中所有相邻重复项
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
输入:“abbaca”
输出:“ca”
解释:例如,在 “abbaca” 中,我们可以删除 “bb” 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa” 可以执行重复项删除操作,所以最后的字符串为 “ca”。
这道题也可以用栈来解决。碰到一个字母,我们将其放入栈中,以上面的示例来看,先把a放入栈中,再把b放入栈中,到第三个字母时,发现和第二个字母(目前栈中最后一个元素)一样,就出栈。
这道题不是很难,但难点在于想到用栈的方式去解决问题。栈帮助我们记录了遍历数组当前元素时候,前一个元素是什么,这个特性就适合匹配相关的问题。
代码如下:
public static String deleteSameChara(String s){
Stack<Character> s1 = new Stack<>();
for (int i = 0; i < s.length(); i++) {
if(s1.isEmpty()){
s1.push(s.charAt(i));
}else if(s.charAt(i) != s1.peek()){
s1.push(s.charAt(i));
}else{
s1.pop();
}
}
StringBuilder str = new StringBuilder();
while (!s1.isEmpty()){
str.append(s1.pop());
}
return str.reverse().toString();
}
private void reverse(char[] arr){
int left = 0;
int right = arr.length - 1;
char temp;
while(left < right){
temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
}
注意:这里的if(s1.isEmpty())不能和下面的else if写在一起(如果用Deque作为堆栈的话不用在意),用正常的Stack会导致空栈无法peek出结果而报错
逆波兰表达式求值
有效的运算符包括 + , - , * , / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
整数除法只保留整数部分。 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
输入: [“2”, “1”, “+”, “3”, " * "]
输出: 9
解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入: [“4”, “13”, “5”, “/”, “+”]
输出: 6
解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入: [“10”, “6”, “9”, “3”, “+”, “-11”, " * ", “/”, " * ", “17”, “+”, “5”, “+”]
输出: 22
逆波兰表达式:是一种后缀表达式,所谓后缀就是指运算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:
去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。
代码如下:
public static void evalRPN(String[] arr){
int temp1;
int temp2;
Stack<Integer> s1 = new Stack<>();
for (int i = 0; i < arr.length; i++) {
for (String s : arr) {
if("+".equals(s)){
temp1 = s1.pop();
temp2 = s1.pop();
s1.push(temp1 + temp2);
}else if("-".equals(s)){
temp1 = s1.pop();
temp2 = s1.pop();
s1.push(temp2 - temp1);
}else if("*".equals(s)){
temp1 = s1.pop();
temp2 = s1.pop();
s1.push(temp1 * temp2);
}else if("/".equals(s)){
temp1 = s1.pop();
temp2 = s1.pop();
s1.push(temp2 / temp1);
}else{
s1.push(Integer.valueOf(s));
}
}
}
}
这里需要注意的是-和/,它们是有先后顺序的,按照入栈的顺序,temp1应该是除(减)数,temp2才是被除(减)数