代码随想录第十一天 | LeetCode 20.有效的括号 LeetCode:1047. 删除字符串中的所有相邻重复项 三、LeetCode:150. 逆波兰表达式求值
前言
注意:我的很多思路都会在代码中的注释中显示,因此要注意看代码,当然,前面的文章更会透漏我的思路。主要·是栈的一些应用和细节的处理。
一、LeetCode:20. 有效的括号
题目解析:括号是一一对应的,且它先出现的话,它的另一半就会后出现。这和栈的先进后出非常的相似。因此感觉这道题目是真的令人惊喜的感觉到出的好妙。
其实本题目不是有效括号的就只有三种情况:1.多出一个左括号。2.两个括号不匹配。3.多出一个右括号。因此更可以总结出来,1+3 = 如果括号的个数是奇数的话,那么一定不是有效括号。
因此核心的一个思考:我们怎么去判断是否是匹配项呢,其实匹配最好的判断就是是否相等。所以我们在读入左括号的时候就直接存入它的另一半右括号,等会弹出右括号直接和便利的右括号进行比较,如果相等的话就是匹配的,不相等就是情况二,如果栈不为空,那么就是多出了一个左括号,如果最后栈已经用完,但是我们还没有遍历完的话,那就是多出了一个右括号,只有当遍历的右括号和栈中的右括号同时遍历完并且每个都相等时,才可以说所有的括号都是有效。
代码实现时也有一个很重要的逻辑:我们肯定是用for循环来往栈里存放前一半,后一半就要来判断是否匹配了。1,2,3,三种情况我们要如何判断呢,顺序非常的重要,我们在for循环中遍历的是括号数组的元素,因此,我们遍历完了数组,证明此时右括号已经遍历完了,已经尽力了,数量为零。那就是已经匹配,左边也已经合格,就看栈里的右括号了,他就决定了到底可不可以通过。(其实还有一点需要理解清楚,就是说在for循环里我们也是要判断栈是不是空,这里的操作排除了栈里的小于数组里的数量的情况了,我们在外面还要判断大于的情况。)
一些思路我都会写在代码的注释中
C++代码如下:
class Solution {
public:
bool isValid(string s) {
if (s.size() % 2 != 0) return false; // 如果s的长度为奇数,一定不符合要求
stack<char> st;
for (int i = 0; i < s.size(); i++) {//先用一层for循环往栈里存入元素,并且注意不是简单存放
if (s[i] == '(') st.push(')');
else if (s[i] == '{') st.push('}');
else if (s[i] == '[') st.push(']');
// 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
// 第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false
//这一步很是精妙,简单说:有没有问题,没问题我就接着往下走。有问题直接退出。
else if (st.empty() || st.top() != s[i]) return false;//在我还在for循环时,你是肯定不可以empty的。
else st.pop(); // st.top() 与 s[i]相等,栈弹出元素
}
// 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
return st.empty();
}
};
JAVA代码如下
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new LinkedList<>();
char ch;
for (int i = 0; i < s.length(); i++) {//一次for循环搞定
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();
}
}
二、LeetCode:1047. 删除字符串中的所有相邻重复项
题目解析:一一对应,需要消除,其实这类的问题用栈来讲非常的容易。(不是删除完一个就完事了,是所有的都不可以删除。)在本题中,栈中储存的是已经遍历过的元素。(这一想其实也可以用哈希来写。)
重要思路:两个元素相同的话是两个都删除掉,这里还要谈到的是if else的先后逻辑,我们其实可以说 if(如果是空或者说是不相等的话,我们都要去加入栈)else(其他情况)。也可以说 if(栈不为空并且不相等为一种情况),else(其他情况)。这个归根到底是我们对遍历过程中入栈和出栈的情况的考查。
C++代码:
class Solution {
public:
string removeDuplicates(string S) {
stack<char> st;//一个栈
for (char s : S) {//遍历我们的目标数组。
if (st.empty() || s != st.top()) {//循环往下的一个条件。再之后->可以添加到栈的元素。
st.push(s);//加入栈
} else {// s 与 st.top()相等的情况
st.pop(); //不需要入栈,反而要弹出。因为我们要加入两个元素。
}
}
string result = "";
while (!st.empty()) { // 将栈中元素放到result字符串汇总
result += st.top();
st.pop();
}
reverse (result.begin(), result.end()); // 此时字符串需要反转一下,因为是进入了一次栈嘛~~
return result;
}
};
C++代码二如下:
class Solution {
public:
string removeDuplicates(string S) {
stack<char> st;
for (char s : S) {
if(!st.empty() && st.top() == s ){
st.pop();
}else{
st.push(s);
}
}
string result = "";
while (!st.empty()) { // 将栈中元素放到result字符串汇总
result += st.top();
st.pop();
}
reverse (result.begin(), result.end()); // 此时字符串需要反转一下
return result;
}
};
JAVA代码如下:
class Solution {
public String removeDuplicates(String s) {
char[] cs = s.toCharArray();
Deque<Character> d = new ArrayDeque<>();
for (char c : cs) {//循环往下的一个条件。再之后->可以添加到栈的元素。
if (!d.isEmpty() && d.peekLast().equals(c)) {
d.pollLast();
} else {
d.addLast(c);
}
}
StringBuilder sb = new StringBuilder();
while (!d.isEmpty()) sb.append(d.pollLast());
sb.reverse();//因为一次进栈出栈,所以我们要重新反转一下
return sb.toString();
}
}
//模拟栈 拿字符串直接作为栈,省去了栈还要转为字符串的操作。
class Solution {
public String removeDuplicates(String s) {
// 将 res 当做栈
StringBuffer res = new StringBuffer();
// top为 res 的长度
int top = -1;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// 当 top > 0,即栈中有字符时,当前字符如果和栈中字符相等,弹出栈顶字符,同时 top--
if (top >= 0 && res.charAt(top) == c) {
res.deleteCharAt(top);
top--;
// 否则,将该字符 入栈,同时top++
} else {
res.append(c);
top++;
}
}
return res.toString();
}
}
// 拓展:双指针
class Solution {
public String removeDuplicates(String s) {
char[] ch = s.toCharArray();
int fast = 0;
int slow = 0;
while(fast < s.length()){
// 直接用fast指针覆盖slow指针的值
ch[slow] = ch[fast];
// 遇到前后相同值的,就跳过,即slow指针后退一步,下次循环就可以直接被覆盖掉了
if(slow > 0 && ch[slow] == ch[slow - 1]){
slow--;
}else{
slow++;
}
fast++;
}
return new String(ch,0,slow);
}
}
三、LeetCode:150. 逆波兰表达式求值
题目解析:逆波兰表达式:是一种后缀表达式,所谓后缀就是指运算符写在后面。如 ( 1 + 2 ) * ( 3 + 4 ) 。该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) .
逆波兰表达式主要有以下两个优点:1.去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。2.适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。
用栈来实现:遇到数字就入栈,遇到出栈就弹出来运算并且是一次弹出弹出两个元素(因为一个运算符一次只能有前后两个元素)。
总之:栈可以进行已知元素的匹配问题。
C++代码如下
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;//一个栈结构
for (int i = 0; i < tokens.size(); i++) {
if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {//判断有符号就开始弹出并且记录两个元素。
int num1 = st.top();//取元素1
st.pop();//弹元素1
int num2 = st.top();//取元素2
st.pop();//弹元素2
//再将结果添加到栈。以方便下一次计算。
if (tokens[i] == "+") st.push(num2 + num1);
if (tokens[i] == "-") st.push(num2 - num1);
if (tokens[i] == "*") st.push(num2 * num1);
if (tokens[i] == "/") st.push(num2 / num1);
} else {
st.push(stoi(tokens[i]));
}
}
int result = st.top();//取出最后的结果
st.pop(); // 把栈里最后一个元素弹出(其实不弹出也没事)
return result;
}
};
JAVA代码如下:(JAVA的遇到符号是直接整体的进行了处理,直降当成一个整体了,没有在设置一个数来代表那个值,直接求出来然后入栈。)
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();
}
}
总结
今天的内容比较简单,但是还是可以再进行一些优化。主要是栈的一些典型的特性吧,相邻,或者一一匹配的问题还是可以想到用栈的。