C++数据结构之栈,队列,树(力扣题解)

目录

栈:

队列:

 1:用栈实现队列

2:用队列实现栈

3:有效的括号

栈!匹配的神!消消乐的神!

4:删除字符串中的所有相邻重复项

5:逆波兰表达式求值(离大谱)

6:滑动窗口最大值(困难单调队列)

二叉树

 二叉树的遍历:

二叉树的递归遍历方式:

二叉树的迭代遍历方式:栈


栈:

特性:先进后出

C++实现:

#include <stack>
using namespace std;
stack<type>st;

队列:

特性:先进先出

C++实现:

#include <queue>
using namespace std;
queue<int> que;

具体的STL库函数就不介绍了,我推荐一位博主大佬^  ^的文章

讲原理肯定是用C来实现的^  ^ ,但这里是题解^  ^,所以用的是C++

 【两万字精编】蓝桥杯算法竞赛系列第0章——蓝桥必考点及标准模板库STL(下)_安然无虞的博客-CSDN博客https://bit-runout.blog.csdn.net/article/details/121383654

 1:用栈实现队列

 思路解析:

重点:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作

 初始化:

stack<int> stIn;//用来放
stack<int> stOut;//用来输出

push():

void push(int x) 
{
   stIn.push(x);//在输入栈里插入元素即可
}

pop():

int pop() 
{
       if(stOut.empty())//如果输出的栈为空
       {
            while(!stIn.empty())//输入的栈为非空,也就是输入栈里还有元素时
            {
                stOut.push(stIn.top());//将输入栈的栈顶的元素插入输出栈里
                stIn.pop();//删除输入栈的栈顶(注意此时的top会--)
            }
        }
        int result=stOut.top();//将栈顶元素存入result;
        stOut.pop();//删除输出栈的栈顶元素
        return result;//返回被删除的元素
}

解释:由于队列是先进先出,所以被删除的是在队列最前面的数

而栈只能删除栈顶的元素,画个图,图解一下

 通过两个输入输出栈的转换,我们就实现了队列的头删

peek():

int peek() {
        int res=this->pop();
        stOut.push(res);
        return res;
    }

解析:很简单,因为我们已经用pop找到了头元素,接收这个头元素并返回即可

empty():

bool empty() {
        return stIn.empty()&&stOut.empty();
    }

解释:如果输入和输出栈均为空,那么就返回true,反之返回false;

完整代码:

class MyQueue {
public:
    stack<int> stIn;
    stack<int> stOut;
    MyQueue() {

    }
    
    void push(int x) {
        stIn.push(x);
    }
    
    int pop() {
        if(stOut.empty())
        {
            while(!stIn.empty())
            {
                stOut.push(stIn.top());
                stIn.pop();
            }
        }
        int result=stOut.top();
        stOut.pop();
        return result;
    }
    
    int peek() {
        int res=this->pop();
        stOut.push(res);
        return res;
    }
    
    bool empty() {
        return stIn.empty()&&stOut.empty();
    }
};

2:用队列实现栈

思路解析:

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppop 和 empty)。 

核心思路:

由于栈的特点是后入先出,所以在队列1中,我们要实现后面的元素先出来,我们即可让前面的元素先存到另外一个队列2,然后此时在队列1中,后面的元素即可变为头元素,按照性质直接出来

class MyStack {
public:
    queue<int>que1;
    queue<int>que2;
    MyStack() {

    }
    
    void push(int x) {
        que1.push(x);
    }
    
    int pop() {
        int size=que1.size();//获取长度
        size--;//目的是为让que1只剩一个元素
        while(size--)//将que1的前元素放入que2里
        {
            que2.push(que1.front());
            que1.pop();
        }
        int result=que1.front();//删除的元素就是que1仅剩的那个元素
        que1.pop();
        que1=que2;//将que2的赋值给que1,重新初始化
        while(!que2.empty())//将que2里的元素删空
        {
            que2.pop();
        }
        return result;//返回在que1里被删除的元素
    }
    
    int top() {
        return que1.back();//队列可以直接查找到队列的尾
    }
    
    bool empty() {
        return que1.empty();//队列2在不进行删除时,都是为空的
    }
};

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack* obj = new MyStack();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->top();
 * bool param_4 = obj->empty();
 */

3:有效的括号

 思路解析:

栈!匹配的神!消消乐的神!

(1)遍历字符串

(2)如果遇到左括号,则插入与它相对应的右括号

(3)如果栈顶元素与遍历到的右括号相等,那么就删除栈顶元素

判断:如果遍历还没有结束,栈就为空,那么返回false;

如果遍历结束,栈为空,返回true,反之栈不为空,返回fasle;

 代码实现:

class Solution {
public:
    bool isValid(string s) {
        stack<int> 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(']');
            // 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
            // 第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false
            else if (st.empty() || st.top() != s[i]) return false;
            else  if(st.top()==s[i]) st.pop(); // st.top() 与 s[i]相等,栈弹出元素
        }
        // 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
        return st.empty();
    }
};

4:删除字符串中的所有相邻重复项

 思路解析:

本题与上题相同,均是属于消消乐类型

(1)遍历字符串

(2)如果与栈顶元素相等则删除栈顶元素

反之,插入栈里

(3)此时栈里的元素就是没办法继续消消乐的元素,由于只能逆序弹出

所以我们在返回时需要再将字符串逆序

 代码实现:

class Solution {
public:
    string removeDuplicates(string S) {
        stack<char> st;
        for (char s : S) {
            if (st.empty() || s != st.top()) {
                st.push(s);
            } else {
                st.pop(); // s 与 st.top()相等的情况
            }
        }
        string result = "";
        while (!st.empty()) { // 将栈中元素放到result字符串汇总
            result += st.top();//将栈中元素逆序加入字符串中
            st.pop();
        }
        reverse (result.begin(), result.end()); // 此时字符串需要反转一下
        return result;

    }
};

更巧的方法:直接将字符串作为栈

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;
    }
};

5:逆波兰表达式求值(离大谱)

 相信看到这个样例你是有点懵的

再看几个样例

 直接给我整自闭,题目都看不明白,其实这里题目就是用了栈的思考方式

可以看这个动图,就明白了

代码随想录https://www.programmercarl.com/0150.%E9%80%86%E6%B3%A2%E5%85%B0%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B1%82%E5%80%BC.html思路解析:

(1)遍历字符串

(2)如果遍历到的元素为加减乘除,那么就让它的前两个数字进行加减乘除

如果遍历到的元素不为加减乘除,那么就让该字符转为数字

(3)最后栈里只剩一个元素,弹出这个元素,返回即可

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 num1=st.top();
            st.pop();
            int num2=st.top();
            st.pop();
            if (tokens[i] == "+") st.push(num2 + num1);
            if (tokens[i] == "-") st.push(num2 - num1);
            if (tokens[i] == "*") st.push(num2 * num1);
            if (tokens[i] == "/") st.push(num2 / num1);
        }
        else
        {
            st.push(stoi(tokens[i]));
        }
    }
    int result=st.top();
    st.pop();
    return result;
    }
};

6:滑动窗口最大值(困难单调队列)

思路解析:

设计单调队列的时候,pop,和push操作要保持如下规则:

  1. pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
  2. push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止
  3. 单调队列设计好后,滑动窗口的简单遍历即可
  4. 代码随想录代码随想录PDF,代码随想录百度网盘,代码随想录知识星球,代码随想录八股文PDF,代码随想录刷题路线,代码随想录知识星球八股文https://www.programmercarl.com/0239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.html(动图展示)

 核心:在k个数范围内的单调队列(只有一个数也算单调),那么只要取出最开头的元素,那么它就一定是k个数内最大的数;至于删除:只有在原数组中遍历到这个这个位置,并且处于首端,那么才会被删除(需要慢慢体会)

class Solution {
private:
    class MyQueue { //单调队列(从大到小)
    public:
        deque<int> que; // 使用deque来实现单调队列
        // 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
        // 同时pop之前判断队列当前是否为空。
        void pop(int value) {
            if (!que.empty() && value == que.front()) {
                que.pop_front();
            }
        }
        // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
        // 这样就保持了队列里的数值是单调从大到小的了。
        void push(int value) {
            while (!que.empty() && value > que.back()) {
                que.pop_back();
            }
            que.push_back(value);

        }
        // 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
        int front() {
            return que.front();
        }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;
        for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
            que.push(nums[i]);
        }
        result.push_back(que.front()); // result 记录前k的元素的最大值
        for (int i = k; i < nums.size(); i++) {
            que.pop(nums[i - k]); // 滑动窗口移除最前面元素
            que.push(nums[i]); // 滑动窗口前加入最后面的元素
            result.push_back(que.front()); // 记录对应的最大值
        }
        return result;
    }
};

二叉树

二叉树的实现:

struct TreeNode {
	int val;
	TreeNode* left;
	TreeNode* right;
};

 二叉树的遍历:

  • 深度优先遍历
    • 前序遍历(递归法,迭代法)
    • 中序遍历(递归法,迭代法)
    • 后序遍历(递归法,迭代法)
  • 广度优先遍历
    • 层次遍历(迭代法)

二叉树的递归遍历方式:

前序遍历:中左右

class Solution {
public:
    void traversal(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        vec.push_back(cur->val);    // 中
        traversal(cur->left, vec);  // 左
        traversal(cur->right, vec); // 右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};

中序遍历:左中右

void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    traversal(cur->left, vec);  // 左
    vec.push_back(cur->val);    // 中
    traversal(cur->right, vec); // 右
}
vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};

后序遍历:左右中

void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    traversal(cur->left, vec);  // 左
    traversal(cur->right, vec); // 右
    vec.push_back(cur->val);    // 中
}
vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};

二叉树的迭代遍历方式:栈

前序遍历:中左右

解释:注意因为,出栈和进栈的顺序相反,所以是先进右再进左

struct TreeNode {
	int val;
	TreeNode* left;
	TreeNode* right;
};
//前序遍历
vector<int> preorderTraversal(TreeNode* root)
{
	stack<TreeNode*>st;
	vector<int> result;
	if (root == NULL) return result;
	st.push(root);//先放中间结点
	while (!st.empty())//当栈非空则继续
	{
		TreeNode* node = st.top();//将*node赋值为栈顶元素
		st.pop();//删除栈顶元素
		result.push_back(node->val);//将栈顶元素指向的val尾插入result;
		if (node->right) st.push(node->right);//如果结点的右边不为空,则入栈
		if (node->left) st.push(node->left);//如果结点的左边不为空,则入栈
	}
	return result;
}

后序遍历:左右中

解释因为:先序遍历为:中左右   后序遍历为:左右中

那么我们可以在先序遍历中变为:中右左,然后在把result数组反转即可得到正确的后序遍历

struct TreeNode {
	int val;
	TreeNode* left;
	TreeNode* right;
};
vector<int> postorderTraversal(TreeNode* root)
{
	stack<TreeNode*>st;
	vector<int> result;
	if (root == NULL) return result;
	st.push(root);//节点入栈
	while (!st.empty())
	{
		TreeNode* node = st.top();
		st.pop();
		result.push_back(node->val);

	}
}

中序遍历:

解释:一开始就从节点的左边开始找,边找边存入栈,直到遇到NULL,开始出栈并尾插入result数组

struct TreeNode {
	int val;
	TreeNode* left;
	TreeNode* right;
};
vector<int> inorderTraversal(TreeNode* root)
{
	vector<int>result;
	stack<TreeNode*>st;
	TreeNode* cur = root;
	while (cur != NULL || !st.empty())
	{
		if (cur != NULL)//不为空
		{
			st.push(cur);//入栈
			cur = cur->left;//cur继续找最左边的节点
		}
		else
		{
			cur = st.top();//如果为空,说明该上一个节点为最左边的
			st.pop();//删除栈顶
			result.push_back(cur->val);//将原栈顶指向的val尾插入result
			cur = cur->right;//访问右节点
		}
	}
	return result;
}

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值