总言
主要内容:编程题举例,熟悉理解以栈此类数据结构为主的题型。
文章目录
1、栈
栈(Stack)是一种重要的数据结构,具有后进先出(LIFO,Last In First Out)的特性。在算法设计和实现中,栈的应用非常广泛,此类题常见以模拟为主,但关键在于能否想到使用栈来完成。
1、①可以直接使用C++标准库提供了std::stack
模板类(实现了栈的功能)。②也可以使用数组或其它结构直接模拟栈的思想特性。
2、除了下述习题,这里附带其它有关栈的习题链接:
收录一(有效的括号、用队列实现栈、用栈实现队列、设计循环队列);
收录二(最小栈、栈的压入与弹出序列、逆波兰表达式求值)
2、删除字符中的所有相邻重复项(easy)
题源:链接。
2.1、题解
1)、思路分析
说明:根据题意,要判断当前元素是否为重复项,则需要知道其前一个元素的信息,可用「栈」来保存信息,直接取栈顶元素与当前遍历元素判断即可。
这里有一个优化细节,若直接使用「栈」std::stack
来模拟整个过程,结束后还需要从栈中将最终获取到的结果取出。因此,不如直接用「数组」模拟栈特性结构:这里以数组尾部为栈顶,「尾插尾删」即「进栈出栈」。如此一来,数组中存留的内容就是最后的需要返回的结果。
2)、题解
注意细节:string::back()
的使用:
If the string is not empty, the function never throws exceptions (no-throw guarantee).
Otherwise, it causes undefined behavior.
class Solution {
public:
string removeDuplicates(string s) {
string ret(s,0,1);//用数组模拟栈,栈堆即数组尾部。
for(int i = 1; i < s.size(); ++i)//遍历,入栈
{
if(!ret.empty() && s[i] == ret.back()) //栈不为空,且栈顶元素与当前待入栈元素相同
ret.pop_back();// 出栈
else
ret.push_back(s[i]);// ⼊栈
}
return ret;
}
};
简化版写法:实则为各接口的调用。
class Solution {
public:
string removeDuplicates(string s) {
string ret; // 搞⼀个数组,模拟栈结构即可
for (auto ch : s) {
if (ret.size() && ch == ret.back())//栈中有元素并且栈顶元素和当前遍历到的元素相同
ret.pop_back(); // 出栈
else
ret += ch; // ⼊栈
}
return ret;
}
};
3、比较含退格的字符串(easy)
题源:链接。
3.1、题解
1)、思路分析
需要理解这里退格的含义:实则为退格键(Backspace)的功能操作,使光标左移一格。
举例:对于给定字符串ab#
,当前文本框中输入为ab
,遇到#
退格,则文本框汇中输入为a
。
整体思路: 此题可划分为模拟。①先根据给定字符串获得模拟后的文本结果,②再比较两字符串是否相同。
对①模拟过程,由于这里退格符合「后进先出」的特性,因此可以使用「栈」结构来模拟退格的过程。为了方便统计结果,可使用「数组」(这里是字符串string)来模拟实现栈结构。
当遇到⾮ # 字符时,直接进栈;
当遇到 # 时,栈顶元素出栈。
此外,需要注意题目提示:对空文本输入退格字符,文本继续为空。 在模拟中,则表示栈中元素为空时,不能再继续出栈。
2)、题解
class Solution {
public:
string newString(string& s)
{
string ret;//模拟栈结构,数组尾为栈顶
for(auto ch : s)
{
if(ch == '#')
{
if(!ret.empty()) //防止连续多个####导致文本为空,此时不能再删除(空文本输入退格字符,文本继续为空:"y#f#o##f")
ret.pop_back();
}
else ret += ch;
}
return ret;
}
bool backspaceCompare(string s, string t) {
//获取输出结果
string news = newString(s);
string newt = newString(t);
//比较
return news.compare(newt) == 0;//也可以直接比较:return newString(s) == newString(t);
}
};
4、基本计算器 II(medium)
题源:链接。
4.1、题解
1)、思路分析
注意这里提示内容,这里的基本计算器减少了实际需要处理的工作量。根据题目可以得知以下细节内容:
1、运算符优先级:给定字符串中只有「加减乘除」四个运算,没有括号等改变优先级。可以知道的是乘除运算在加减之前。
2、字符串中有空格,非有效字符,需要对其处理。
3、大数提取:由于是字符串,这里给定的运算数不一定只有个位,如XXX ▢ XX ,因此这里涉及对多位数的字符提取(相关操作之前遇到过:高精度求和)。
此题题解: 利用栈模拟计算过程vector<int>
,先计算乘除法,其结果用栈保存,最后对栈中剩余元素进行求和(加减法),即最终结果。
根据遇到的运算符,分情况讨论即可。
使用一个变量op来记录遇到的操作符,在遍历时:
1、若遇到操作符op:更新当前操作符。
2、若遇到数字,①先将完整的运算数num1
提取出来,②根据最新一次保存的操作符op,判断后续操作。
a、若op == '+'
,将运算数num1
入栈;
b、若op == '-'
,将运算数的相反数-num1
入栈。
c、若op == '*'
,提取栈顶元素num2,与num1进行乘运算,再将运算结果放回栈中。(这里简化操作:可以直接将num1乘到栈顶元素上(使用vector
作为栈,back()返回引用值)。)
d、若op == '/'
,提取栈顶元素num2,与num1进行除运算,再将运算结果放回栈中。(这里简化操作:可以直接将num1除到栈顶元素上(使用vector
作为栈,back()返回引用值)。)
2)、题解
class Solution {
public:
int calculate(string s) {
char op = '+';//保存最新一次操作符
vector<int> tmp;//用于模拟栈(保存有历史运算数)
int i = 0; int len = s.size();
while(i < len)
{
//当前i是否为空格
if(s[i] == ' ') i++;
//判断当前位置是操作符还是运算数
if(!isdigit(s[i]) && s[i] != ' ')
{ //当前遍历到的是操作符
op = s[i++];//更新op,遍历下一个位置
}
else
{ //当前遍历到的是操作数
//1、提取操作数
int num1 = 0;//当前遍历提取到的运算数
while(i < len && isdigit(s[i]))
{
num1 = num1*10 + (s[i]-'0');
i++;
}
//2、根据该数的左侧操作符(op中保存的值),判断是否运算
if(op == '+') tmp.push_back(num1);
else if(op == '-') tmp.push_back(-num1);
else if(op == '*' || op == '/')
{ //提取栈顶元素做运算
int num2 = 0;
if(!tmp.empty())//栈中有元素时
{
num2 = tmp.back();
tmp.pop_back();//删除栈顶元素
}
//将运算结果入栈
if(op == '*') tmp.push_back(num1*num2);
else if(op == '/') tmp.push_back(num2 / num1);//注意除法这里的运算顺序
}
}
}
//将栈中元素累加
int ret = 0;
for(auto num : tmp)
ret += num;
//返回最终运算结果
return ret;
}
};
vector::back()
返回引用,可直接对值修改。
reference back();
const_reference back() const;
//2、根据该数的左侧操作符(op中保存的值),判断是否运算
if(op == '+') tmp.push_back(num1);
else if(op == '-') tmp.push_back(-num1);
else if(op == '*') tmp.back() *= num1;//reference back();可直接修改值
else if(op == '/') tmp.back() /= num1;//注意除法这里的运算顺序
5、字符串解码(medium)
题源:链接。
5.1、题解
1)、思路分析
同上一题,使用栈模拟,分情况讨论。
解析此题,相对复杂的是嵌套情况:2[a3[b]]
,需要先解码内层的3[b]
获得bbb
,才能对外层2[abbb]
进行解码。 因此,我们使用双栈来模拟,一个用于存放数字k(重复出现的次数),一个用于存放该数字对应的字符(重复k次的字符串)。分情况讨论如下:
1、遇到数字:提取出这个数字,放入”数字栈"中;
2、遇到'['
:把后面的字符串提取出来,放入“字符串栈”中;
3、遇到']'
:可以进行一次解析,将结果放回"字符串栈"栈顶的字符串后面;
4、遇到单独的字符:提取出来这个字符串,直接放在"字符串栈"栈顶的字符串后面
细节:字符串这个栈中,先放入一个空串。
2)、题解
class Solution {
public:
string decodeString(string s) {
//双栈
stack<int> nums;//存放数字的栈
stack<string> strs;//存放字符串的栈
strs.push("");
int i = 0; int len = s.size();
//遍历,模拟过程,分情况讨论
while(i < len)
{
if(isdigit(s[i]))//若当前遍历到的是数字
{
//因数组可能不止一位,需要提取数字,再放入数字栈中
int k = 0;
while(i < len && isdigit(s[i]))
k = k*10 + s[i++] -'0';
nums.push(k);
}
else if(s[i] == '[')//若当前遍历到的是左括号
{
++i;//跳过该'[',提取'['之后的字符串,放入字符栈中
string tmp;
while(i < len && isalpha(s[i]))
tmp += s[i++];
strs.push(tmp);
}
else if(s[i] == ']')//若遇到右括号,可进行一次解码操作
{
//分别提取数字栈和字符栈的栈顶元素
int k = nums.top();
nums.pop();
string str = strs.top();
strs.pop();
//解码,存放入字符栈中;
string tmp;
while(k--) tmp += str;
strs.top() += tmp;//value_type& top();
++i;//当前情况处理结束,可以跳过该']',继续后续的遍历
}
else if(isalpha(s[i]))//若遇到的是字符
{
//提取字符,将其放在栈顶元素之后。
string tmp;
while(i < len && isalpha(s[i]))
tmp += s[i++];
strs.top() += tmp;
}
}
return strs.top();
}
};
6、验证栈序列(medium)
题源:链接。
6.1、题解
1)、思路分析
用栈来模拟进出栈的流程: 一直让元素进栈,进栈的同时判断是否需要出栈。当所有元素模拟完毕之后,如果栈中还有元素,那么就是一个非法的序列。否则,就是一个合法的序列。
2)、题解
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> st;
int i = 0;//用于指向当前poped中待出栈元素
for(auto x : pushed)
{
st.push(x);//让元素入栈
//判断当前入栈元素是否是出栈元素
while(!st.empty() && st.top() == popped[i])
{
st.pop();
i++;
}
}
return st.empty();
}
};