题目与题解
20. 有效的括号
题目链接:20. 有效的括号
代码随想录题解:20. 有效的括号
解题思路:
经典栈应用题,以前做过好几次了。将括号依次压入栈中,如果栈顶括号与下一个括号配对,则将栈顶元素弹出;反之则压入栈。遍历结束后再判断栈是否为空即可。
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
if (stack.isEmpty()) {
stack.push(s.charAt(i));
continue;
}
if ('(' == stack.peek() && ')' == s.charAt(i)
|| '{' == stack.peek() && '}' == s.charAt(i)
|| '[' == stack.peek() && ']' == s.charAt(i)) {
stack.pop();
} else {
stack.push(s.charAt(i));
}
}
return stack.isEmpty();
}
}
看完代码随想录之后的想法
基本思路差不多,分析很全面,但是做法有点不一样。随想录给出的括号不匹配情况如下:
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
具体做法是,如果遍历得到的元素是左括号,则将应该匹配的右括号压入栈中;如果遍历元素是右括号,则判断该元素与当前的栈顶元素是否相同,相同则弹出,否则就直接返回失败。最后判断栈是否为空即可。
相比之下,这样写会更快的发现输入是否符合条件。因为一旦碰到右括号不匹配的情况,就说明一定没有符合要求的左括号,可以直接返回错误了。
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 deque.isEmpty();
}
}
遇到的困难
无
1047. 删除字符串中的所有相邻重复项
题目链接:1047. 删除字符串中的所有相邻重复项
代码随想录题解:1047. 删除字符串中的所有相邻重复项
解题思路:
跟上一题有些类似,同样是匹配问题,碰到符合匹配条件的就将元素弹出栈,不匹配就压栈。这一题中匹配条件就是栈顶元素和遍历到的元素是否相同。遍历完成后,由于栈中元素的顺序是倒序,还需要把它正序输出。
class Solution {
public String removeDuplicates(String s) {
Stack<Character> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
if (stack.isEmpty()) {
stack.push(s.charAt(i));
} else {
if (stack.peek() == s.charAt(i)) {
stack.pop();
} else {
stack.push(s.charAt(i));
}
}
}
char[] result = new char[stack.size()];
for (int i = stack.size() - 1; i >= 0; i--) {
result[i] = stack.pop();
}
return new String(result);
}
}
看完代码随想录之后的想法
自己的写法是随想录java方法的第一种写法,但是写的有些重复和复杂了,这题可以把需要压栈的情况写在一起,用stack.isEmpty() || stack.peek() != s.charAt(i),就可以少写一层判断。
另外,随想录还给出了两种写法,也很有启发性。
一个是用StringBuilder或者StringBuffer这种可以修改内容的字符串类直接作为栈,一边处理输入字符的同时,一边就将正确的元素加入,就不需要最后一步再将栈中元素倒序出来,给一个新的字符串赋值;
还有一种是用双指针法,方法类似之前的移除元素,快慢指针先一起出发,碰到前后字符相同时,快指针前进一步,慢指针不动;每次循环之前,都将快指针对应的值赋予慢指针。最后将[0,slow]之间的字符串返回即可。
遇到的困难
无
150. 逆波兰表达式求值
题目链接:150. 逆波兰表达式求值
代码随想录题解:150. 逆波兰表达式求值
解题思路:
逆波兰表达式其实就是将普通的算式,按照实际计算顺序排布的一种公式。这题也是栈的经典应用题,本身没有什么太多的算法,更多是要模拟这样的过程。
实际计算时,先设置一个栈,用于存放输入的数字或计算结果。对输入的字符串数组进行遍历,如果字符串是数字,则直接压栈,如果字符串是符号,则要将栈顶的两个元素取出来,对其根据符号计算后,再将计算结果压栈。直到遍历完成,栈中应只剩一个元素,该元素就是最后的计算结果,弹出即可。
写的时候需要注意,因为出栈顺序和压栈顺序是相反的,因此涉及到减法和除法这两种计算时,栈顶的元素才是被减数和被除数,栈顶下一层的元素是减数和被减数。
另外,由于输入都是字符串格式,所以需要将其转换为数字类型才能计算。Java自带的Integer.parseInt()方法可以直接转化。
class Solution {
public int evalRPN(String[] tokens) {
Stack<String> stack = new Stack<>();
for (int i = 0; i < tokens.length; i++) {
if ("+".equals(tokens[i])) {
int a = Integer.parseInt(stack.pop());
int b = Integer.parseInt(stack.pop());
stack.push(""+(b+a));
} else if ("-".equals(tokens[i])) {
int a = Integer.parseInt(stack.pop());
int b = Integer.parseInt(stack.pop());
stack.push(""+(b-a));
} else if ("*".equals(tokens[i])) {
int a = Integer.parseInt(stack.pop());
int b = Integer.parseInt(stack.pop());
stack.push(""+(b*a));
} else if ("/".equals(tokens[i])) {
int a = Integer.parseInt(stack.pop());
int b = Integer.parseInt(stack.pop());
stack.push(""+(b/a));
} else {
stack.push(tokens[i]);
}
}
return Integer.parseInt(stack.pop());
}
}
看完代码随想录之后的想法
还是代码更简洁一点。
首先,可以设置一个元素类型为Integer的栈,压栈的时候用Integer.valueOf()将其实际值压入即可,如果读取到的是符号,就可以直接通过判断进行计算,无需将其压栈。
对于加法和乘法,数字的顺序不影响计算,无需临时变量保存栈顶两个元素,直接用stack.pop()相加或相乘即可;减法也是一种特殊的加法,用-stack.pop()+stack.pop()即可实现;只有除法需要把栈顶数字临时取出来再计算。
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new LinkedList();
for (String s : tokens) {
if ("+".equals(s)) { // leetcode 内置jdk的问题,不能使用==判断字符串是否相等
stack.push(stack.pop() + stack.pop()); // 注意 - 和/ 需要特殊处理
} else if ("-".equals(s)) {
stack.push(-stack.pop() + stack.pop());
} else if ("*".equals(s)) {
stack.push(stack.pop() * stack.pop());
} else if ("/".equals(s)) {
int temp1 = stack.pop();
int temp2 = stack.pop();
stack.push(temp2 / temp1);
} else {
stack.push(Integer.valueOf(s));
}
}
return stack.pop();
}
}
遇到的困难
我一开始在本地跑的很好,一次就通过了,但是提交上去的时候,总是抛出异常,说‘*’的值不是数字类型,不能放入Integer.parseInt。因为本地几个测试用例都通过了,我就觉得很神奇,想来想去也没想出来,所以先瞄了一眼答案,发现在判断输入是否为符号时,我用的是==,而答案用了equals。改掉以后果然就对了。
之前学java的时候隐约记得equals和==有一点区别,但平常没有碰到什么错误,所以两种都有在用。百度了一下两者之间的区别如下:
- “==”是一个运算符,用于比较变量或对象的值,当比较两个基本数据类型时,它比较的是它们的值是否相等;当比较两个引用数据类型时,它比较的是这两个对象的内存地址是否相同。
- equals()是Object类中的一个方法,用于比较两个对象是否相等,如果一个类没有重写equals()方法,那么它调用的实际上是“==”方法,比较的是对象的内存地址;如果一个类重写了equals()方法,通常是为了比较两个对象的内容是否相等,例如,String类就重写了equals()方法,用于比较两个字符串的内容是否相同。
所以,当我在这里用==时,实际比较的是常量字符串“*”和当前字符String[i]的引用对象是否一样,那结果肯定是不一样的,所以判断就出错了。而用equals就只是判断两者的值是否相等,符合要求。今后一定要多注意。
今日收获
强化了一下栈的应用学习,了解了java中==和equals的区别。题目本身都不难,难点在于如何看到题目能想到用栈去解决。栈是最适合匹配问题的,希望日后一看到就能会用吧。