20. 有效的括号
这题有这种匹配(局部对称)的特征,用堆栈先进先出的结构很方便,通过遍历左括号,将右括号压栈,再在遍历到右括号的时候匹配栈内元素完成,很有哈希表算A+B = target用 A= target - B的意思,具体要考虑以下三种失效的情况。
- 遍历到右括号的时候,如果和栈顶元素不匹配,代表括号种类不匹配。
- 遍历到右括号的时候,如果栈内是空的,代表右括号是多余的,或者说没有左括号跟右括号匹配。
- 遍历完字符串时,如果栈还不是空,代表左括号是多余的,因为没有对应的右括号将栈内元素弹出。
当遍历完字符串且栈为空,则代表匹配。
C++
class Solution {
public:
bool isValid(string s) {
stack<char> st;
for (int i = 0; i < s.size(); i++) {
if (s[i] == '(') st.push(')');
else if (s[i] == '[') st.push(']');
else if (s[i] == '{') st.push('}');
else if (st.empty() || s[i] != st.top()) return false;
else st.pop();
}
return st.empty();
}
};
C#
public class Solution {
public bool IsValid(string s) {
Stack<char> st = new Stack<char>();
for (int i = 0; i < s.Length; i++) {
if (s[i] == '(') st.Push(')') ;
else if (s[i] == '[') st.Push(']');
else if (s[i] == '{') st.Push('}');
else if (st.Count == 0|| s[i] != st.Peek()) return false;
else st.Pop();
}
return st.Count == 0;
}
}
1047. 删除字符串中的所有相邻重复项
知道栈为什么适合做这种类似于爱消除的操作,因为栈帮助我们记录了遍历数组当前元素时候,前一个元素是什么。
二刷看一下双指针模拟堆栈法,和C#的StringBuilder。
C++
class Solution {
public:
string removeDuplicates(string S) {
string result;
for(char s : S) {
if(result.empty() || result.back() != s) {
result.push_back(s);
}
else {
result.pop_back();
}
}
return result;
}
};
也可以直接把string当栈用。
class Solution {
public:
string removeDuplicates(string s) {
string result;
for (int i = 0; i < s.size(); i++) {
if (result.empty() || result.back() != s[i]) result.push_back(s[i]);
else result.pop_back();
}
return result;
}
};
C#
Stack,Queue定义的时候记得new。
public class Solution {
public string RemoveDuplicates(string s) {
Stack<char> st = new Stack<char>();
char[] a = s.ToCharArray();
for (int i = 0; i < a.Length; i++) {
if(st.Count == 0 || a[i] != st.Peek()) st.Push(a[i]);
else st.Pop();
}
Array.Resize(ref a, st.Count);
for(int i = a.Length - 1; i >= 0; i--) {
a[i] = st.Pop();
}
return new string(a);
}
}
150. 逆波兰表达式求值
思路是每扫到运算符就取两个栈元素做运算,注意减和除两个数的顺序,运算结果再压回栈。
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 n1 = st.top();
st.pop();
int n2 = st.top();
st.pop();
int tmp = 0;
if (tokens[i] == "+") tmp = n1 + n2;
if (tokens[i] == "-") tmp = n2 - n1;
if (tokens[i] == "*") tmp = n1 * n2;
if (tokens[i] == "/") tmp = n2 / n1;
st.push(tmp);
}
else {
st.push(stoi(tokens[i]));
}
}
return st.top();
}
};
C#
C#可以用int.TryParse更方便,且siwtch可以判断字符串(C++siwtch只能判断整形)。
public class Solution {
public int EvalRPN(string[] tokens) {
int num = 0;
Stack<int> stack = new Stack<int>();
for (int i = 0; i< tokens.Length; i++) {
if (int.TryParse(tokens[i], out num)) {
stack.Push(num);
} else {
int num1 = stack.Pop();
int num2 = stack.Pop();
switch (tokens[i])
{
case "+":
stack.Push(num1 + num2);
break;
case "-":
stack.Push(num2 - num1);
break;
case "*":
stack.Push(num1 * num2);
break;
case "/":
stack.Push(num2 / num1);
break;
}
}
}
return stack.Pop();
}
}
二刷
有效的括号
三种情况,左括号多余,右括号多余,左右不匹配,分别对应访问完字符串栈还有东西,访问过程栈为空了,还有就是访问元素和栈顶不匹配,可以反向往栈里放东西,这样代码更简洁,一开始写有点麻烦了。
- 匹配和消除问题是栈的强项。
class Solution {
public:
bool isValid(string s) {
stack<char> data;
for (char a : s) {
if (a == '(' || a == '[' || a == '{') {
data.push(a);
}
if (a == ')') {
if (!data.empty() && data.top() == '(') data.pop();
else return false;
}
if (a == '}') {
if (!data.empty() && data.top() == '{') data.pop();
else return false;
}
if (a == ']') {
if (!data.empty() && data.top() == '[') data.pop();
else return false;
}
}
if (data.empty()) return true;
else return false;
}
};
//简化
class Solution {
public:
bool isValid(string s) {
stack<char> data;
for (char a : s) {
if (a == '(') data.push(')');
else if (a == '[') data.push(']');
else if (a == '{') data.push('}');
else if (data.empty() || a != data.top()) return false;
else data.pop();
}
return data.empty();
}
};
删除字符串中的所有相邻项
利用栈保存了上一个元素,直接判断,而且处理完栈顶的一部分,之前的自动就会露出来(这点很重要!)。
- 注意空栈不要调用top(),会超尾-1报错。
class Solution {
public:
string removeDuplicates(string s) {
stack<char> data;
for (char a : s) {
if (data.empty()) {
data.push(a);
}
else if (a != data.top()) {
data.push(a);
}
else {
data.pop();
}
}
string ans = "";
while (!data.empty()) {
ans += data.top();
data.pop();
}
reverse(ans.begin(), ans.end());
return ans;
}
};
或者用双指针方法(用数组单指针遍历一遍,中间消除后可能会出现新的匹配项比如abba,bb消掉后aa也要消掉,所以需要双指针),slow不断维护,fast遍历。
逆波兰表达式求值
还是用到栈处理完一部分弹出去,之前的自动暴露出来继续处理的特性,每次遇到运算符就弹出来两个数组计算完再把结果压进去,注意弹出来的顺序和运算的顺序是反的。
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> data;
for (string it : tokens) {
if (it != "+" && it != "-" && it != "*" && it != "/") data.push(stoi(it));
else {
int it1 = data.top();
data.pop();
int it2 = data.top();
data.pop();
if (it == "+") data.push(it2 + it1);
if (it == "-") data.push(it2 - it1);
if (it == "*") data.push(it2 * it1);
if (it == "/") data.push(it2 / it1);
cout << it1 << it << it2 << " = " <<data.top() << endl;
}
}
return data.top();
}
};