队列:先进先出 栈:后进先出
队列:先进先出 栈:后进先出
有效的括号
https://leetcode.cn/problems/valid-parentheses/
做法:遍历字符串
1.当前字符是左括号:进栈
2.当前字符是右括号:出栈顶元素和当前字符比较是否匹配
- 特殊情况:如果此时栈为空,那么说明不匹配
3.最后如果栈为空,说明是匹配的,否则是不匹配的
class Solution {
public:
bool isValid(string s) {
stack<char> st;
//遍历字符串,碰到左括号:进栈 碰到右括号:出栈顶元素判断是否和该右括号匹配
for(auto& ch:s)
{
if(ch == '(' || ch == '[' || ch == '{')
{
st.push(ch);
}
else
{
//如果栈为空,说明括号是不匹配的
if(st.empty()) return false;
//出栈顶元素和当前右括号匹配
char top = st.top();
st.pop();
if( ch ==')' && top != '(' || ch == ']' && top != '[' ||ch == '}' && top != '{')
return false;
}
}
return st.empty(); //如果最后栈为空,说明括号是匹配的
}
};
用队列实现栈
https://leetcode-cn.com/problems/implement-stack-using-queues/
两个队列实现栈
队列:先进先出 栈:后进先出
两个队列:
- NonEmptyQueue:表示存放数据的队列,记为A
- EmptyQueue:表示空队列,记为B
插入元素:直接往A队列插入
弹出元素:将A队列的元素都插入到B队列当中,直到A队列只剩下一个元素,此时这个元素就相当于是栈顶元素。然后A队列和B队列交换,保证B队列是空的
例如:插入顺序为1 2 3 4
A队列:1 2 3 4
假设要栈顶元素:那么就算A队列队尾元素
假设要弹出栈顶元素:将A的元素放到B,直到A只有一个元素,然后弹出A队列剩余的一个元素就是栈顶元素,然后交换两个队列
A:4 B:1 2 3 ==> A:1 2 3 B:空
获取栈顶元素:本质就是A队列的队尾元素
判空:A队列和B队列为空 就是空
class MyStack {
public:
MyStack() {
}
void push(int x) {
NonEmptyQueue.push(x);//往不为空的队列插入数据
}
int pop() {
//将不空队列的数据放到空队列当中,直到不空队列只有一个元素
while(NonEmptyQueue.size() > 1)
{
EmptyQueue.push(NonEmptyQueue.front());
NonEmptyQueue.pop();
}
int front = NonEmptyQueue.front();
NonEmptyQueue.pop();
NonEmptyQueue.swap(EmptyQueue);//交换两个队列,保证EmptyQueue为空队列
return front;
}
int top() {
return NonEmptyQueue.back();
}
bool empty() {
return EmptyQueue.empty() && NonEmptyQueue.empty();
}
private:
queue<int> NonEmptyQueue;//不为空的队列
queue<int> EmptyQueue;//空队列
};
一个队列实现栈
将队列类比为一个循环圈
插入元素:直接插入
弹出元素:将队列的前size-1个元素重新插入到队列当中,然后此时队头元素就是要弹出的===>队头元素就相当于是栈顶元素
获取栈顶元素:相当于是队尾元素
class MyStack {
public:
MyStack() {
}
void push(int x) {
q.push(x);
}
int pop() {
int size = q.size();
//将前size-1个元素重新插入到队列当中
for(int i = 0;i<size-1;i++)
{
int front = q.front();
q.pop();
q.push(front);
}
//此时队头元素就相当于是栈顶元素
int front = q.front();
q.pop();
return front;
}
int top() {
return q.back();
}
bool empty() {
return q.empty();
}
private:
queue<int> q;
};
用栈实现队列
https://leetcode-cn.com/problems/implement-queue-using-stacks/
两个栈:
- pushStack:存放插入数据的栈
- popStack:存放要弹出数据的栈
插入元素:直接往pushStack插入
子函数Check:检查是否要将pushStack的内容导入到popStack
- 将pushStack的所有元素插入到popStack ===>元素就变成逆序了
插入顺序:1 2 3 4
pushStack: 1 2 3 4 导入到popStack后:4 3 2 1 符合先进先出
弹出队头元素:先进行检查 =>弹出popStack栈顶元素
获取队头元素:先进行检查 =>返回popStack栈顶元素
class MyQueue {
public:
MyQueue() {
}
void push(int x) {
pushStack.push(x);
}
void Check() //检查是否要将push栈的内容导入到pop栈
{
if(popStack.empty())
{
while(!pushStack.empty())
{
popStack.push(pushStack.top());
pushStack.pop();
}
}
}
int pop() {
Check();
int top = popStack.top();
popStack.pop();
return top;
}
int peek() {
Check();
return popStack.top();
}
bool empty() {
return pushStack.empty() && popStack.empty();
}
private:
stack<int> pushStack;//存放数据的栈
stack<int> popStack;//用于弹出数据的栈
};
设计循环队列
https://leetcode-cn.com/problems/design-circular-queue/
方式1:使用数组实现
分析:
1.存储k个元素,需要开辟k+1
个空间!否则不能准确的判空和判满
2.需要使用两个变量来标志队头和队尾位置,记为front
和tail
,还需要一个变量size:记录队列当中存储的元素个数
enQueue: 向循环队列插入一个元素
- 如果队列为满了,直接插入失败
- 在tail位置插入元素,然后tail++往后走,注意:需要和size+1取模,防止tail越界
deQueue:从循环队列删除元素
- 如果队列为空,删除失败
- 只需要front往后走,front++即可,注意:需要和size+1取模,防止越界
获取队头元素:front指向的就是队头元素
获取队尾元素:注意:tail的前一个位置就是队尾元素!但是不能直接返回:arr[tail-1],因为有可能上一次放的位置是size位置,然后tail++取模后到了0位置,所以要判断一下
- 如果tail不是0位置:返回arr[tail-1]
- 如果tail是0位置:返回arr[size]
判断队列是否为满:(tail + 1) % (size+1) == front;
class MyCircularQueue {
public:
MyCircularQueue(int k) {
arr.resize(k+1);//开辟k+1个空间
front = tail = 0;
size = k;
}
bool enQueue(int value) { //向循环队列插入一个元素。如果成功插入则返回真
if(isFull()) return false;
arr[tail] = value;
tail ++;
tail %= size+1; //tail = tail % (size+1)
return true;
}
bool deQueue() { //从循环队列中删除一个元素。如果成功删除则返回真。
if(isEmpty()) return false;
front++;
front %= size+1;
return true;
}
int Front() {
if(isEmpty()) return -1;
return arr[front];
}
int Rear() {
if(isEmpty()) return -1;
//由于插入元素之后,tail往后走,所以tail的前一个元素才是队尾元素
if(tail == 0) return arr[size];
else return arr[tail-1];
}
bool isEmpty() {
return front == tail;
}
bool isFull() {
return (tail + 1) % (size+1) == front;
}
private:
vector<int> arr;
int front;//标志队头
int tail;//标志队尾
int size;//实际存储的元素个数
};
方法2:链表实现
存储k个元素,需要构建k+1个节点的循环链表,还需要定义两个指针front和tail标志队头和队尾,最初链表为空二者都指向链表的头
向循环队列插入一个元素:队列为满,插入失败。否则插入到tail节点位置,然后tail节点往后走
从循环队列删除一个元素:队列为空,删除失败。否则front节点往后走
获取队头元素:返回front指针指向的节点的存放的值
获取队尾元素:注意:tail节点的前一个节点才是队尾节点,所以需要从front位置开始往后遍历找到tail的前一个节点,然后返回对应的值
判空:front == tail
判满:tail->next == front
class MyCircularQueue {
public:
MyCircularQueue(int k) {
//构建k+1个节点的循环链表
tail = head = new ListNode;
for(int i = 0;i<k;i++)
{
ListNode* newnode = new ListNode;
tail->next = newnode;
tail = tail->next;
}
//tail指向尾节点,让首尾相连
tail->next = head;
//注意:要让tail回到起始位置!!!!!因为一开始链表没有有效元素
head = tail;
}
bool enQueue(int value) { //向循环队列插入一个元素。如果成功插入则返回真。
if(isFull()) return false;
tail->val = value;
tail = tail->next;
return true;
}
bool deQueue() {
if(isEmpty()) return false;
head = head->next;
return true;
}
int Front() {
if(isEmpty()) return -1;
return head->val;
}
int Rear() {
if(isEmpty()) return -1;
//从head位置遍历查找,tail的前一个节点放的就算队尾元素
ListNode* cur = head;
while(cur->next != tail)
cur = cur->next;
return cur->val;
}
bool isEmpty() {
return head == tail;
}
bool isFull() {
return tail->next == head;
}
private:
ListNode* head;
ListNode* tail;
};s
最小栈
https://leetcode.cn/problems/min-stack/
方法1:使用两个栈,一个数据栈,一个辅助栈
- 1.如果最小栈为空,就存放当前入栈数据
- 2.如果最小栈不为空,
case1:如果此时最小栈的栈顶元素>此时入数据栈的当前数 -> 最小栈中压入当前数
case2:如果此时最小栈的栈顶元素<=此时入数据栈的当前数 -> 最小栈中重复压入此时最小栈的栈顶元素
最小栈和辅助栈,同步压入元素,同步弹出元素,每时每刻得到当前数据栈中数据的最小值 ,二者一一对应
class MinStack {
public:
MinStack() {
}
void push(int val) {
dataStack.push(val);
//判断要往辅助栈放重复元素还是更小的元素
if( minStack.empty() || minStack.top() >= val)
minStack.push(val);
else
minStack.push(minStack.top());//重复压入当前最小栈栈顶元素
}
void pop() {
int top = dataStack.top();
dataStack.pop();
minStack.pop(); //坑点!!此时minStack也要同步pop数据
}
int top() {
return dataStack.top();
}
int getMin() {
return minStack.top();
}
private:
stack<int> minStack;//辅助栈->存放当前栈中对应的最小值
stack<int> dataStack;
};
优化:
- 1.如果最小栈为空,就存放当前入栈数据
- 2.如果最小栈不为空
case1:如果此时最小栈的栈顶元素**>=**此时入数据栈的当前数 -> 最小栈中压入当前数 ,等于的时候也要插入!!!
case2:如果此时最小栈的栈顶元素<此时入数据栈的当前数 -> 不压入数据
pop数据时:如果此时数据栈的数据和最小栈的栈顶数据一样->最小栈出栈顶元素 否则最小栈不出数据
class MinStack {
public:
MinStack() {
}
void push(int val) {
dataStack.push(val);
if( minStack.empty() || minStack.top() >= val)
minStack.push(val);
}
void pop() {
int top = dataStack.top();
dataStack.pop();
if(top == minStack.top())
minStack.pop();
}
int top() {
return dataStack.top();
}
int getMin() {
return minStack.top();
}
private:
stack<int> minStack;//辅助栈->存放当前栈中对应的最小值
stack<int> dataStack;
};
栈的压入&弹出序列
https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&&tqId=11174&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
思想就是:使用栈的入栈顺序,模拟这个出栈顺序,如果能模拟出来,就说明是合法的,否则就是非法的!
做法:
使用一个栈,然后遍历pushV数组,模拟入栈顺序,还需要定义一个变量popIndex
表示出栈数组遍历到了哪个位置,对于每一次遍历:
- 先将当前pushV数组的元素进栈
- 如果栈不为空,并且当前出栈元素 = 栈顶元素,那么就弹出栈顶元素 ==> while循环操作
最后:如果popIndex遍历完了popV数组,说明是可以按这个顺序弹出的,返回true
class Solution {
public:
//pushV:元素的入栈顺序 popV:判断该序列是否可能为该栈的弹出顺序
bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
if(pushV.size() != popV.size()) return false;
stack<int> st;//模拟进栈顺序
int popIndex = 0;//当前出栈元素位置
for(int i = 0;i<pushV.size();i++)
{
//当前元素进栈
st.push(pushV[i]);
//如果栈不为空,并且当前出栈元素 = 栈顶元素,那么就弹出
while(!st.empty() && st.top() == popV[popIndex])
{
popIndex++;
st.pop();
}
}
//最后:如果popIndex遍历完了popV数组,说明是可以按这个顺序弹出的
return popIndex == popV.size();
}
};
逆波兰表达式(后缀表达式)
https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
思路:遍历字符串
- 如果是操作数:进栈
- 如果是操作符:取栈顶的两个元素出来进行运算,然后再把运算结果放到栈中
- 最后遍历完成后,栈顶元素就是计算结果
注意:栈是后进先出的,所以先拿到的是右操作数,然后才是左操作数
最好是使用switch-case语句,因为有+-*/
四种情况,后续如果还有其它情况,也比较容易增加。如果使用if-else,看起来不美观文章来源:https://www.toymoban.com/news/detail-656192.html
- 使用第一个字符判断是操作符还是操作数的时候,要特别小心
-
,因为操作数可能为负数,所以要特判:如果字符串长度为1:说明就是操作符,否则就是操作数 - 或者说:取字符串的最后一个字符判断是操作数还是操作符
switch(str.back())
stoi
函数:用于将string字符串转为整数文章来源地址https://www.toymoban.com/news/detail-656192.html
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
//遍历容器,如果是操作数就进栈
int left = 0,right = 0;
auto GetNum = [&]() //获取两个操作数
{
right = st.top(); //注意:先拿到的是右操作数
st.pop();
left = st.top();
st.pop();
};
for(auto& str:tokens) //str是string
{
//如果是操作数就进栈,如果是操作符就把当前栈中两个元素拿出来进行运算,然后把运算结果进栈
switch(str[0])
{
case '+':
GetNum();
st.push(left + right);
break;
case '-': //坑点:有可能不是操作符,而是操作数->负数
if(str.size() == 1) //操作符
{
GetNum();
st.push(left - right);
}
else //操作数
{
st.push(stoi(str));
}
break;
case '*':
GetNum();
st.push(left * right);
break;
case '/':
GetNum();
st.push(left / right);
break;
default: //操作数
st.push(stoi(str));
break;
}
}
return st.top();
}
};
到了这里,关于【数据结构】栈和队列常见题目的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!