题解链接:
栈的拿手好戏!| LeetCode:20. 有效的括号_哔哩哔哩_bilibili
20. 有效的括号
思路:最先出现的右括号肯定是由最邻近的最括号进行抵消的,所以记录左括号的应该需要满足后进先出的格式。然后考虑这样几个边界问题:1、右括号为什么不要进栈?(因为右括号进栈无法再出栈,右括号只是起到提取栈内元素的作用) 2、出栈的括号与当前右括号不匹配怎么办,做哪些操作。(当前括号不匹配,则表明这个右括号无法匹配,而括号匹配是不可以越过括号的,所以直接返回false)
其实在最早使用c语言基于数组构建栈的时候,只需要一个尾部指针即可完成构建。所以对于解题减少时间开销,就可以基于所给出的数组,使用静态指针直接在当前数组上作为栈实现解题。给出两种解法)
// 时间复杂度都是O(n),空间复杂度为o(n),时间开销全在构建的额外的辅助Stack的pop、push操作上
class Solution {
public boolean isValid(String s) {
// 思路:最先出现的右括号肯定是由最邻近的最括号进行抵消的,所以记录左括号的应该需要满足后进先出的格式
// 这样几个问题:1、右括号为什么不要进栈? 2、出栈的括号与当前右括号不匹配怎么办,做哪些操作
// 构建栈进行求解
// if(s.length() == 0 || s.length() == 1 || s.length()%2 != 0)
// return false;
// Stack<Character> stack = new Stack<>();
// for(int i=0; i<s.length(); i++){
// if(s.charAt(i) == '(' || s.charAt(i) == '[' || s.charAt(i) == '{'){
// stack.push(s.charAt(i));
// }
// else{
// if(s.charAt(i) == ')' && stack.isEmpty() == false && stack.peek() == '(') stack.pop();
// else if(s.charAt(i) == ']' && stack.isEmpty() == false && stack.peek() == '[') stack.pop();
// else if(s.charAt(i) == '}' && stack.isEmpty() == false && stack.peek() == '{') stack.pop();
// else return false;
// }
// }
// if(stack.isEmpty() == true)
// return true;
// return false;
// 解法二,就是利用给出的数组,结合定义静态栈指针实现计算,本质上就是快慢双指针,时间效率会得到大幅提升
// 时间最快的还得是通过递归来实现,但需要结合题目来看是否可以使用递归,下面的150就可以
if(s.length() == 0 || s.length() == 1 || s.length()%2 != 0)
return false;
char[] ch = s.toCharArray();
Map<Character,Character> map = new HashMap<>();
map.put(')','(');
map.put(']','[');
map.put('}','{');
int j = -1;
for(int i=0; i<ch.length; i++){
// 出现右括号了
if(map.containsKey(ch[i]) == true){
if(j>=0 && map.get(ch[i]) == ch[j])
j--;
else
return false;
}
else
ch[++j] = ch[i];
}
if(j == -1)
return true;
return false;
}
}
1047. 删除字符串中的所有相邻重复项
思路:相邻的元素抵消,是后进先出思想的使用;此外抵消之后,前部分元素可能与之后的元素继续抵消,也即持续出栈,因此使用栈来解题呼之欲出。另外使用静态指针的方式继续减少时间开销。并且不管在什么操作上,只要是涉及读取元素,首要考虑采用数组。
// 时间复杂度O(n),空间复杂度大于O(n),因为多构建了一个StringBuilder
class Solution {
public String removeDuplicates(String s) {
// 每一次删除两个,而且是删除的位置是相邻的元素,那么又是采用栈可以很好的解决,
// 因为返回的结果是字符串,而栈无法直接将字符拼接成字符串进行返回,因此可构建数组实现栈的模拟,而这里的数组可使用StringBuilder来模拟,
// StringBuilder比StringBuffer速度快很多
StringBuilder builder = new StringBuilder();
char[] ch = s.toCharArray();
int j = -1;
for(int i=0; i<ch.length; i++){
if(j >= 0 && ch[i] == ch[j])
j--;
else
ch[++j] = ch[i];
}
for(int i=0; i<=j; i++)
builder.append(ch[i]);
return builder.toString();
// 但是使用数组进行数据的读取与比较比基于StringBuilder的读取来的快的多,所以只需要采用StringBuilder作为结果返回的容器即可
// StringBuilder builder = new StringBuilder();
// int j = -1;
// for(int i=0; i<s.length(); i++){
// if(j >= 0 && s.charAt(i) == builder.charAt(j))
// builder.deleteCharAt(j--);
// else
// builder.insert(++j, s.charAt(i));
// }
// return builder.toString();
}
}
150. 逆波特兰表达式求值
class Solution {
public int evalRPN(String[] tokens) {
// 采用静态指针实现基于已有数组模拟栈
// 本质上可以视作为快慢双指针解题
// 时间开销大幅减少
int j = 0;
for (int i = 0; i < tokens.length; i++) {
if (tokens[i].equals("+") || tokens[i].equals("-") || tokens[i].equals("*") || tokens[i].equals("/")) {
int b = Integer.parseInt(tokens[--j]);
int a = Integer.parseInt(tokens[--j]);
switch (tokens[i]){
case "+": tokens[j++] = Integer.toString(a+b); break;
case "-": tokens[j++] = Integer.toString(a-b); break;
case "*": tokens[j++] = Integer.toString(a*b); break;
case "/": tokens[j++] = Integer.toString(a/b); break;
}
}
else{
tokens[j++] = tokens[i];
}
}
return Integer.parseInt(tokens[0]);
}
}