删除字符串中所有相邻的重复项
解法——利用栈模拟
这样的过程像小时候的消消乐,有相同的元素就抵消,不同的留下来。并且题目中说了反复执行该删除操作。这个过程就很想数据结构栈里的,和栈顶元素相同就弹出,不同继续入栈。
这里我们并不需要真正用容器中的栈,如果要用容器,还需要将其还原成字符串。并且最终返回结果出栈时是个逆序的形式,我们还需要个reverse才能返回最后结果。所以我们可以使用string来模拟栈。
a,b进入string,无特殊情况,当第二个b进入时string里的b比较发现相同,删除string里的b,继续指针向后移
class Solution
{
public:
string removeDuplicates(string s)
{
string ret; // 搞⼀个数组,模拟栈结构即可
for(auto ch : s)
{
if(ret.size() && ch == ret.back()) ret.pop_back(); // ret里有字符且相等时出栈
else ret += ch; // ⼊栈
}
return ret;
}
};
比较含退格的字符串
解法:利用栈模拟
遍历a时进栈,遍历b时进栈,当遍历到#时,执行退格操作(弹出栈顶元素)。遍历完毕后先将两个字符串转化成最终形式再进行比较即可。这里还是用字符串模拟栈。
class Solution
{
public:
bool backspaceCompare(string s, string t)
{
return changeStr(s) == changeStr(t);
}
string changeStr(string& s)
{
string ret; // ⽤数组模拟栈结构
for(char ch : s)
{
if(ch != '#') ret += ch;
else //当ret里有字符时,执行退格操作
{
if(ret.size()) ret.pop_back();
}
}
return ret;
}
};
基本计算器II
**解法一 利用双栈模拟计算:**其中一个栈维护数字,另一个栈维护操作符。(在基本计算器I中使用)
**解法一优化:只用一个栈: **因为这里只有加减乘除四则运算,不涉及括号,也没有负数。创建一个int类型的栈来存放数字。再用一个char变量表示运算符。刚开始将char初始化为+,为了接下来操作统一。
接下来开始遍历数字(要把它从字符串中提前出来)当遇到第一个数字时,不要提取,先把他放入栈中,因为我们在数字前面初始化了+,我们不知道数字下一个位置的运算符是什么,如果是乘除,这个数字就要和后面的运算符结合。接着继续更新箭头,箭头指向减号,此时char里更新为-。因为栈里存储的未经过运算的数都是+或者-,当最终弹出栈里的数,肯定会执行加法运算,所以可以大胆覆盖更新char存储的运算符号。箭头继续移动到3,观察char中存储的是-,那就将他的相反数放入栈中(此时存储他的相反数-3,保证了最后弹出栈的数字都进行加法运算)。
当箭头移动到4时,此时char里存储的是*,此时把栈顶元素弹出来计算(因为本题中没有括号,乘除法的优先级为最高,所以可以大胆将其弹出进行运算)执行完4*6之后,将24继续放入栈里。除法同理
当箭头移动到123,非个位数时,需要将他提取出来,我们此时搞一个tmp,先将其初始化为0,然后遇到一位数将tmp扩大10倍加上去,即移动到1时,010+1=1;移动到2时,110+2=12;移动到3时,12*10+3=123.此时char里存储的是-,将-123存储到栈里。
解法二:把中缀表达式转化成后缀表达式(将波兰表达式转化为逆波兰表达式,在基本计算器I中使用)
class Solution
{
public:
int calculate(string s)
{
vector<int> st; // ⽤数组来模拟栈结构
int i = 0, n = s.size();
char op = '+';
while(i < n)
{
if(s[i] == ' ') i++; //空格直接跳过
//当前字符是数字字符
else if(s[i] >= '0' && s[i] <= '9')
{
// 先把这个数字给提取出来
int tmp = 0;
while(i < n && s[i] >= '0' && s[i] <= '9') //遇到非个位数提取
tmp = tmp * 10 + (s[i++] - '0');
//+ - 直接入栈,* / 与栈顶元素计算
if(op == '+') st.push_back(tmp);
else if(op == '-') st.push_back(-tmp);
else if(op == '*') st.back() *= tmp;
else st.back() /= tmp;
}
//当前箭头所指字符是操作符
else
{
op = s[i]; //更新op
i++;
}
}
int ret = 0;
for(auto x : st) ret += x;
return ret;
}
};
字符串解码
方框嵌套需要由内到外。
方框嵌套与运算中括号相似,都是需要由内向外计算,这里还是需要借助栈。从头开始遍历,遇到1、[ 、a、2、[ 、b、c都先存入栈中。当遇到 ] 时,把3和c拿出来进行解码,因为此时会有一个左方框与之匹配。因为我们解码是拿最近存储的进行解码,符合栈后进先出的规则。我们可以定义两个栈结构,⼀个⽤来保存解码前的重复次数 k (左括号前的数字),⼀个⽤来保存解码之前字符串的信息(左括号前的字符串信息)
刚开始遇到数字,要先把数字提取出来,放到int栈里,碰到左括号,将左括号后的字符提取出来放入string栈里。然后碰到右括号,将两个栈栈顶元素拿出来。因为右括号要去匹配最近的左括号。
当2和bc解码之后变为bcbc,但此时不能直接放入栈中,因为不确定bcbc前面是否连着其他字符,所以要把bcbc放入栈顶元素后面。之后继续向后移动发现第二个右括号,此时将1和abcbc拿出来解码,但是此时栈顶元素为空,就不能直接将他放入栈顶元素后面,否则会造成越界访问的风险,我们可以在最开始时在栈里放入一个空字符串。
继续向后访问,当碰到一个单独的字符串后,将两个字符串直接解析出来,直接放在栈顶元素字符串的后面。
class Solution
{
public:
string decodeString(string s)
{
stack<int> nums;
stack<string> st;
st.push("");
int i = 0, n = s.size();
while(i < n)
{
if(s[i] >= '0' && s[i] <= '9')
{
int tmp = 0;
while(s[i] >= '0' && s[i] <= '9')
{
tmp = tmp * 10 + (s[i] - '0');
i++;
}
nums.push(tmp);
}
else if(s[i] == '[')
{
i++; // 把括号后⾯的字符串提取出来
string tmp = "";
while(s[i] >= 'a' && s[i] <= 'z')
{
tmp += s[i];
i++;
}
st.push(tmp);
}
else if(s[i] == ']')
{
string tmp = st.top();
st.pop();
int k = nums.top();
nums.pop();
while(k--)
{
st.top() += tmp;
}
i++; // 跳过这个右括号
}
else
{
string tmp;
while(i < n && s[i] >= 'a' && s[i] <= 'z')
{
tmp += s[i];
i++;
}
st.top() += tmp;
}
}
return st.top();
}
};
验证栈序列
每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false 。
借助栈:让push一直进栈,进栈的同时判断push是否出栈。在push出栈这里定义一个指针i,指向第一个元素用来标记一下接下来谁要先出栈。让1进栈,此时判断pop中指针指向的将要出栈的元素,1不出继续进栈,2、3、4都接着进栈。此时4要出栈,出栈之后让pop里的指针后移继续指向将要出栈的元素,i指向5,继续判断栈顶元素是否为出栈元素。(此时栈里有123),不是将要出栈的元素就继续进栈。1235.
当发现栈顶元素和将要出栈的元素不一样时,此时看指针是否遍历到最后一个位置,如果不是,就说明这个序列是错误的
当这个栈是合法的时,此时i会移动到最后一个位置,栈为空栈
class Solution
{
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped)
{
stack<int> st;
int i = 0, n = popped.size();
for(auto x : pushed)
{
st.push(x);
while(st.size() && st.top() == popped[i])
{
st.pop();
i++;
}
}
return i == n;
}
};