栈和队列的理论基础
关于两个数据结构的底层实现,详细介绍可见代码随想录的学习网站
栈和队列不是容器,而是容器适配器,底层可以自由控制使用其他容器来实现栈和队列的功能。
-栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。
栈的内部结构,栈的底层实现可以是vector,deque,list, 主要就是数组和链表的底层实现。
指定vector为栈的底层实现,初始化语句如下:
std::stack<int, std::vector<int> > third; // 使用vector为底层容器的栈
-队列中先进先出的数据结构,队列同样不允许有遍历行为,不提供迭代器。
指定list 为起底层实现,初始化queue的语句如下:
std::queue<int, std::list<int>> third; // 定义以list为底层容器的队列
LeetCode232 用栈实现队列
思考:
比较简单的一题,没有涉及算法。把握住栈是先进后出,要实现队列的先进先出。
代码说明在代码块注释中。需要注意的是,队列的弹出操作最终是从出栈中弹出,所以做队列pop操作时需要先确定出栈是否不为空,若为空则需要将入栈的元素倒进出栈中。
再者就是,栈的pop和push操作都是void函数,不会返回一个具体的元素,以下写法是错误的:
int temp=stackIn.pop();
而top函数可以返回一个具体的元素,但是不具备从栈中删除元素的能力。
我的代码:
class MyQueue {
public:
//需要两个栈,一个进栈一个出栈
stack<int> inTo;
stack<int> outTo;
MyQueue() {
}
void push(int x) {
inTo.push(x);
}
int pop() {
//因为队列的弹出操作最终是从出栈中弹出,所以做队列pop操作时需要先确定出栈是否不为空,若为空则需要将入栈的元素倒进出栈中
if(outTo.empty()){
while(!inTo.empty()){
int temp=inTo.top();
inTo.pop();
outTo.push(temp);
}
int temp=outTo.top();
outTo.pop();
return temp;
}
//出栈不为空时可以直接从出栈弹出
else
{
int temp=outTo.top();
outTo.pop();
return temp;
}
}
//获取队列顶部元素,和队列弹出操作类似,但是并不会将元素从队列中弹出删除
int peek() {
//注意相似功能的复用,直接使用已有的pop函数
int temp = this->pop();
// 因为pop函数弹出了元素res,所以再添加回去
outTo.push(temp);
return temp;
}
//两个栈均空时才算队列空
bool empty() {
if(inTo.empty()&&outTo.empty())
return true;
else return false;
}
};
- 时间复杂度: push和empty为O(1), pop和top为O(n)
- 空间复杂度: O(n)
225. 用队列实现栈
思考:也是一道不涉及算法的题,但是相比于用栈实现队列来说会稍微有些绕,主要体现在pop操作的实现。看下图:
分为fin是否为空两种情况
我的代码:
class MyStack {
public:
queue<int> fin;
queue<int> mid;
MyStack() {
}
void push(int x) {
fin.push(x);
}
//
int pop() {
if(fin.empty()){
int temp=mid.front();
mid.pop();
while(!mid.empty()){
fin.push(temp);
temp=mid.front();
mid.pop();
}
return temp;
}
else{
int temp=fin.front();
fin.pop();
while(!fin.empty()){
mid.push(temp);
temp=fin.front();
fin.pop();
}
return temp;
}
}
int top() {
int temp=this->pop();
this->push(temp);
return temp;
}
bool empty() {
if(fin.empty()&&mid.empty())
return true;
else return false;
}
};
- 时间复杂度: pop为O(n),top为O(n),其他为O(1)
- 空间复杂度: O(n)
20. 有效的括号
思考:
很简单的一道用栈解决的题。因为栈先进后出的特性,和括号匹配是适配的,外层的括号后匹配,内层的括号先匹配。
所以思路就是:循环遍历字符串,遇到所有左括号都压入栈中,遇到匹配的右括号就将左括号弹出栈。具体的边界情况处理见代码块注释。
我的代码:
class Solution {
public:
bool isValid(string s) {
stack<char> myStack;
//循环遍历字符串,遇到所有左括号都压入栈中,遇到匹配的右括号就将左括号弹出栈
for(int i=0;i<s.size();i++){
if(s[i]=='('||s[i]=='{'||s[i]=='[')
myStack.push(s[i]);
//如果经过上一步压栈操作之后,此时栈仍为空,说明循环遍历到的是右括号,且没有对应的左括号与右括号相匹配,可以直接返回false
if(!myStack.empty()){
//栈不为空时,继续判断后面遇到的右括号能否和左括号正确匹配,匹配正确就把左括号弹出栈,不正确说明没有对应的左括号在栈中,直接返回false
if(s[i]==')'&&myStack.top()=='(')
myStack.pop();
else if(s[i]==')'&&myStack.top()!='(')
return false;
if(s[i]=='}'&&myStack.top()=='{')
myStack.pop();
else if(s[i]=='}'&&myStack.top()!='{')
return false;
if(s[i]==']'&&myStack.top()=='[')
myStack.pop();
else if(s[i]==']'&&myStack.top()!='[')
return false;
}
else return false;
}
//最后判断空栈说明所有括号都匹配上了,反之不匹配
if(myStack.empty())
return true;
else return false;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(n)
1047. 删除字符串中的所有相邻重复项
思考:
消消乐(?
这道题也不难,利用栈先进后出的性质,栈可以作为 判断是否出现两个相邻且相同的字母 的工具。一边遍历字符串,一边将元素压入栈中;当栈顶元素和当前遍历到的字符串元素相同时,在弹出栈顶元素的同时,利用双指针法修改原字符串s。具体的边界情况处理见代码块注释。
注意最后更改新字符串的长度。
我的代码:
class Solution {
public:
string removeDuplicates(string s) {
//还是用栈解决,两个相同的字母在栈中相遇时,就将它俩全部弹出栈
//同时用双指针法,对应更新字符串s包含的字符
stack<char> myStack;
int j=0;
for(int i=0;i<s.size();i++){
//栈空时直接将字符压入栈中
if(myStack.empty()){
myStack.push(s[i]);
s[j++]=s[i];
}
//否则先判断此时栈顶元素和循环到的串元素是否相同,相同则弹出栈且不更新字符串,不相同则压入栈
else{
char temp=myStack.top();
if(temp==s[i]){
myStack.pop();
j--;
}
else{
myStack.push(s[i]);
s[j++]=s[i];
}
}
}
//最后更新字符串长度
s.resize(j);
return s;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(n)
*文章是本人刷题过程中的一些笔记和理解,记录的解析不一定足够清晰,也可能存在本人暂未意识到的错误,如有问题欢迎大家指出。文章中学习到的解法来自代码随想录的B站视频(栈和队列1~4)以及代码随想录的学习网站。