栈和队专题刷题

栈和队专题刷题

基于双向链表实现双向队列

/* 双向链表节点 */
struct DoubleListNode{
    int val; // 节点值
    DoubleListNode *next; // 后继节点指针
    DoubleListNode *prev; // 前驱节点指针
    DoubleListNode(int val) : val(val),prev(nullptr),next(nullptr){
    }
};

/* 基于双向链表实现的双向队列 */
class LinkedListDeque
{
    private:
        DoubleListNode *front,*rear;// 头节点 front ,尾节点 rear
        int queSize = 0;// 双向队列的长度
        
    public:
    /* 构造方法 */
    LinkedListDeque() : front(nullptr),rear(nullptr){
    }
    
    /* 析构方法 */
    ~LinkedListDeque(){
         // 遍历链表删除节点,释放内存
         DoubleListNode *pre,*cur = front;
         while(cur != nullptr){
             pre = cur;
             cur = cur -> next;
             delete pre;
         }
    }
    
    /* 获取双向队列的长度 */
    int size(){
        return queSize;
    }
    
     /* 判断双向队列是否为空 */
    bool isEmpty()
    {
        return size() == 0;
    }
    /* 入队操作 */
    void push(int num,bool isFront){
        DoubleListNode *node = new DoubleListNode(num);
        // 若链表为空,则令 front 和 rear 都指向 node
        if(isEmpty())
            front = rear = node;
        // 队首入队操作
        else if(isFront){
            // 将 node 添加至链表头部
            front -> prev = node;
            node -> next = front;
            front = node;//更新头结点
        //队尾入队操作
        }else{
            // 将 node 添加至链表尾部
            rear -> next = node;
            node -> prev = rear;
            rear = node;//更新尾结点
        }
        queSize++;// 更新队列长度
        
    }
    
    /* 队首入队 */
    void pushFirst(int num) {
        push(num, true);
    }

    /* 队尾入队 */
    void pushLast(int num) {
        push(num, false);
    }
    
     /* 出队操作 */
     int pop(bool isFront){
         if(isEmpty())
            throw out_of_range("队列为空");
        int val;
        // 队首出队操作
        if(isFront){
            val = front -> val;// 暂存头节点值
            // 删除头节点
            DoubleListNode *fNext = front -> next;
            if(fNext != nullptr){
                fNext -> prev = nullptr;
                fNext -> next = nullptr;
            }
            delete front;
            front = fNext; // 更新头节点
        // 队尾出队操作    
        }else{
            val = rear -> val;// 暂存尾节点值
            // 删除尾节点
            DoubleListNode *rPrev = rear -> prev;
            if(rPrev != nullptr){
                rPrev -> next = nullptr;
                rPrev -> prev = nullptr;
            }
            delete rear;
            rear = rPrev;// 更新尾节点
            
        }
        queSize--;// 更新队列长度
        return val;
     }
    
    /* 队首出队 */
    int popFirst() {
        return pop(true);
    }

    /* 队尾出队 */
    int popLast() {
        return pop(false);
    }
    
    
    /* 访问队首元素 */
    int peekFirst() {
        if (isEmpty())
            throw out_of_range("双向队列为空");
        return front->val;
    }

    /* 访问队尾元素 */
    int peekLast() {
        if (isEmpty())
            throw out_of_range("双向队列为空");
        return rear->val;
    }

    /* 返回数组用于打印 */
    vector<int> toVector() {
        DoublyListNode *node = front;
        vector<int> res(size());
        for (int i = 0; i < res.size(); i++) {
            res[i] = node->val;
            node = node->next;
        }
        return res;
    }
    
    
};

232.用栈实现队列

用两个栈实现一个队列

class MyQueue {
public: 
    stack<int> A,B;

    MyQueue() {

    }
    
    void push(int x) {
        A.push(x);
    }
    
    int pop() {
        //复用peek函数
        int peek = this -> peek();
        B.pop();
        return peek;

    }
    
    int peek() {
        //当B栈没有元素时,将A栈的元素依次弹出放入B栈
        if(B.empty()){
            while(!A.empty()){
            B.push(A.top());
            A.pop();
        }
        }
        
        int res = B.top();
        return res;

    }
    
    bool empty() {
        return A.empty() && B.empty();

    }
};

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

225. 用队列实现栈

一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出元素就是栈的顺序了。

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

    }
    
    void push(int x) {
        q.push(x);
    }
    
    int pop() {
        int size = q.size();
        size--;
        // 将队列头部的元素(除了最后一个元素外) 重新添加到队列尾
        while(size--){
            q.push(q.front());
            q.pop();
        }
        int res = q.front();
        q.pop();
        return res;
    }
    
    int top() {
        return q.back();
    }
    
    bool empty() {
        return q.empty();

    }
};

/**
 * 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();
 */

用两个队来实现(其中q2为辅助队)

class MyStack {
public:
    queue<int> que1;
    queue<int> que2; // 辅助队列,用来备份
    /** Initialize your data structure here. */
    MyStack() {

    }

    /** Push element x onto stack. */
    void push(int x) {
        que1.push(x);
    }

    /** Removes the element on top of the stack and returns that element. */
    int pop() {
        int size = que1.size();
        size--;
        while (size--) { // 将que1 导入que2,但要留下最后一个元素
            que2.push(que1.front());
            que1.pop();
        }

        int result = que1.front(); // 留下的最后一个元素就是要返回的值
        que1.pop();
        que1 = que2;            // 再将que2赋值给que1
        while (!que2.empty()) { // 清空que2
            que2.pop();
        }
        return result;
    }

    /** Get the top element. */
    int top() {
        return que1.back();
    }

    /** Returns whether the stack is empty. */
    bool empty() {
        return que1.empty();
    }
};

20 有效的括号

class Solution {
public:
    bool isValid(string s) {
        stack<char> 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('}');
            else if(st.empty() || st.top() != s[i]) return false;
            // st.top() 与 s[i]相等,栈弹出元素
            else st.pop();
        }
        //遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配
        return st.empty();

    }
};

括号的匹配(z中山大学考研机试题)

在算术表达式中,除了加、减、乘、除等运算外,往往还有括号。

包括有大括号 {},中括号 [],小括号 (),尖括号 <> 等。

对于每一对括号,必须先左边括号,然后右边括号;如果有多个括号,则每种类型的左括号和右括号的个数必须相等;对于多重括号的情形,按运算规则,从外到内的括号嵌套顺序为:大括号->中括号->小括号->尖括号,另外相同的括号可以嵌套。

例如,{[()]},{(())},{{}} 为一个合法的表达式,而 ([{}]),{([])},[{<>}] 都是非法的。

输入格式

第一行包含整数 n�,表示共有 n� 个表达式需要判断。

接下来 n� 行,每行包含一个括号表达式。

输出格式

每行输出一个表达式的判断结果。

如果合法输出 YES,否则输出 NO

数据范围

1≤n≤1001≤�≤100
表达式长度不超过 100100。

输入样例:
5
{[(<>)]}
[()]
<>()[]{}
[{}]
{()}
输出样例:
YES
YES
YES
NO
YES

代码如下:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;

unordered_map<char,int> map;

bool check(string str)
{
    stack<int> stk;
    for(char c : str)
    {
        //先获取进栈元素的优先级
        int p = map[c];
        // //进栈为右括号
        if(p > 0)
        {
            if(stk.empty() || stk.top() != -p) return false;
            stk.pop();
        }
        //进栈为左括号
        else
        {
            if(stk.size() > 0 && abs(p) > abs(stk.top())) return false;
            else stk.push(p);
        }
    }
    return stk.empty();
    
}
int main()
{
    //左括号用负数表示,右括号用正数表示,且数值表示其优先级
    map['{'] = -4 , map['}'] = 4;
    map['['] = -3 , map[']'] = 3;
    map['('] = -2 , map[')'] = 2;
    map['<'] = -1 , map['>'] = 1;
    int T;
    cin>>T;
    while(T--)
    {
        string str;
        cin>>str;
        if(check(str)) puts("YES");
        else puts("NO");
    }
    return 0;
}

https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/

本题也是用栈来解决的经典题目。

那么栈里应该放的是什么元素呢?

我们在删除相邻重复项的时候,其实就是要知道当前遍历的这个元素,我们在前一位是不是遍历过一样数值的元素,那么如何记录前面遍历过的元素呢?

所以就是用栈来存放,那么栈的目的,就是存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。

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

https://leetcode.cn/problems/evaluate-reverse-polish-notation/

//逆波兰表达式(后缀表达式) 一般用栈来处理(栈的经典应用)
 //当碰到运算符就把该运算符前两个值进行运算
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<long long> st;
        for(string c : tokens)
        {
            if(c == "+" || c == "-" || c == "*" || c == "/")
            {
                long long num1 = st.top();
                st.pop();
                long long num2 = st.top();
                st.pop();
                if(c == "+") st.push(num2 + num1);
                if(c == "-") st.push(num2 - num1);
                if(c == "*") st.push(num2 * num1);
                if(c == "/") st.push(num2 / num1);
            }else
            {
                st.push(stoi(c));//stoi 表示 string 转为 int
            }
        }

        int res = st.top();
        return res;

    }
};

239. 滑动窗口最大值

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> ans;
        deque<int> q; // 双端队列
        for (int i = 0; i < nums.size(); i++) {
            // 1. 入
            while (!q.empty() && nums[q.back()] <= nums[i]) {
                q.pop_back(); // 维护 q 的单调性 从后往前移除所有队列中小于等于当前元素的元素
            }
            q.push_back(i); // 入队
            // 2. 出  
            if (i - q.front() >= k) { // 队首已经离开窗口了
                q.pop_front();
            }
            // 3. 记录答案
            if (i >= k - 1) {
                // 由于队首到队尾单调递减,所以窗口最大值就是队首
                ans.push_back(nums[q.front()]);
            }
        }
        return ans;
    }
};

347. 前 K 个高频元素

前置知识:优先队列

常见定义举例

//小顶堆
priority_queue<int,vector<int>,greater<int>> q;
//大顶堆
priority_queue<int,vector<int>,less<int>> q;
//默认大顶堆
priority_queue<int> q;

常用函数

top()
pop()
push()
emplace()
empty()
size()

自定义比较方式

当数据类型并不是基本数据类型,而是自定义的数据类型时,就不能用greater或less的比较方式了,而是需要自定义比较方式

在此假设数据类型是自定义的水果:

string fruit
{
	string name;
	int price;
}

有两种自定义比较方式的方法,如下

1.重载运算符

重载”<”

  • 若希望水果价格高为优先级高,则
//大顶堆
struct fruit
{
	string name;
	int price;
	friend bool operator < (fruit f1,fruit f2)
	{
		return f1.price < f2.price;
	}
};
  • 若希望水果价格低为优先级高
//小顶堆
struct fruit
{
	string name;
	int price;
	friend bool operator < (fruit f1,fruit f2)
	{
		return f1.peice > f2.price;  //此处是>
	}
};

2.仿函数

若希望水果价格高为优先级高,则

//大顶堆
struct myComparison
{
    bool operator () (fruit f1,fruit f2)
    {
        return f1.price < f2.prive;
    }
};
//此时优先队列的定义应该如下
priority_queue<fruit,vector<fruit>,myComparison> q;

本题我们就要使用优先级队列来对部分频率进行排序。

为什么不用快排呢, 使用快排要将map转换为vector的结构,然后对整个数组进行排序, 而这种场景下,我们其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的。

此时要思考一下,是使用小顶堆呢,还是大顶堆?

表面看使用大根堆,那么问题来了,定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么怎么保留下来前K个高频元素呢。而且使用大顶堆就要把所有元素都进行排序,那能不能只排序k个元素呢?

所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

class Solution {
public:
    //小顶堆
    class mycomparison{
        public:
            bool operator() (const pair<int,int> &lhs,const pair<int,int> &rhs){
                //lhs rhs 为map的两个元素,lhs.first是nums里的数key,对应的lhs.second就是出现次数value。
                return lhs.second > rhs.second;//小顶堆是大于号
            }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        //统计元素出现频率
        unordered_map<int,int> map;
        for(int i = 0;i < nums.size();i++){
            map[nums[i]]++;
        }

        //对频率进行排序
        // 定义一个小顶堆,大小为k
        priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparison> q;

        //遍历map中的元素
        //若队列元素个数超过k,则将栈顶元素出栈
        for(auto &a : map){
            q.push(a);
            if(q.size() > k){
                q.pop();
            }
        }
        
        // 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        vector<int> res;
        for(int i = k - 1;i >= 0;i--){
            res.push_back(q.top().first);
            q.pop();
        }
        return res;
    }
};
  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值