LeetCode刷题day10——栈与队列

理论基础

了解一下 栈与队列的内部实现机制,文中 是以C++为例讲解的。

232.用栈实现队列

题目链接/文章讲解/视频讲解

// 栈实现队列  肯定2个栈:输入栈和输出栈
class MyQueue {
public:
    stack<int> stackIn;
    stack<int> stackOut;

    MyQueue() {
        // 怎么定义栈
    }

    // 将元素 x 推到队列的末尾
    void push(int x) {
        stackIn.push(x);
    }

    // 从队列的开头移除并返回元素
    int pop() {
        // 输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入),再从出栈弹出数据
        // 如果输出栈不为空,则直接从出栈弹出数据就可以了。
        if(stackOut.empty()){
            while(!stackIn.empty()){
                stackOut.push(stackIn.top());
                stackIn.pop();
            }
        }  
        int res = stackOut.top();
        stackOut.pop();
        return res;
        
    }

    // 返回队列开头的元素
    int peek() { //注意这个函数的写法
        int res = this->pop(); // 直接使用已有的pop函数
        stackOut.push(res); // 因为pop函数弹出了元素res,所以再添加回去
        return res;
    }

    // 进栈和出栈都为空的话,说明模拟的队列为空
    bool empty() { 
        if (stackIn.empty() && stackOut.empty()) return true;
        else return false;
     }
};

本题收获:
1、代码开发上的习惯问题,在工业级别代码开发中,最忌讳的就是 实现一个类似的函数,直接把代码粘过来改一改就完事了。这样的项目代码会越来越乱,一定要懂得复用功能相近的函数要抽象出来,不要大量的复制粘贴,很容易出问题!
2、在C++中,std::stack的两种push函数分别适用于不同的参数传递方式,这反映了C++的高效内存管理机制:
(1) push(const value_type& x) - 这是一个接受常量引用参数的重载版本。通过传递常量引用,这个函数可以避免拷贝整个对象,从而提高效率。当你只需要向栈中添加一个对象的副本,并且不打算修改传递的对象时,使用这个版本是合适的。
(2) push(value_type&& x) - 这是一个接受右值引用的重载版本。右值引用是C++11新增的特性,用于支持移动语义。当传递一个右值(通常是临时对象)到push函数时,使用这个版本可以直接将对象的内容“移动”到栈中,而不是复制。这可以在处理大型对象时大幅提高性能,因为它避免了不必要的拷贝。
总结来说,常量引用版本用于从已存在的对象中复制数据,而右值引用版本则用于从即将销毁的对象中移动数据,从而优化性能和资源利用。选择合适的函数取决于你的具体需求和对象的状态(是否为临时对象)。

225. 用队列实现栈

只用一个队列就可以模拟栈了。 掌握一个队列的方法,更简单一些。题目链接/文章讲解/视频讲解
本题困难:要明白que2只是备份的作用;pop中没有清空que2以及要把que2赋值给que1才能持续做下一步;

class MyStack {
public:
    queue<int> queue1;
    queue<int> queue2;

    MyStack() {}

    void push(int x) { queue1.push(x); }

    int pop() {
        // 将队列1的元素都压到队列2中,只保留最后一个元素
        int size = queue1.size() - 1;
        while (size--) {
            int val = queue1.front();
            queue1.pop();
            queue2.push(val);
        }
        int res = queue1.front();
        queue1.pop();
        //忘记清空que2
        queue1 = queue2;          // 再将que2赋值给que1
        while (!queue2.empty()) { // 清空que2
            queue2.pop();
        }
        return res;
    }

    int top() {
        // int res = this->pop();
        // queue1.push(res);
        // return res;
        // 直接返回最后一个元素即可
        return queue1.back();
    }

    bool empty() {
        // return queue1.empty() && queue2.empty();
        // 2就是一个备份,可以只用1
        return queue1.empty();
    }
};

C++队列Queue类成员函数如下:
back()返回最后一个元素
empty()如果队列空则返回真
front()返回第一个元素
pop()删除第一个元素
push()在末尾加入一个元素
size()返回队列中元素的个数

20. 有效的括号

讲完了栈实现队列,队列实现栈,接下来就是栈的经典应用了。题目链接/文章讲解/视频讲解
考虑不匹配的情况:
(1) (() 奇数个
(2) (({]]] 偶数个,左右括号数量一样但是不匹配
(3) [{((]} 偶数个,左右括号数量不一样(卡哥思路中的第一种和第三种情况)
但是自己想不出来思路,不知道怎么匹配对应,首先全部入栈还是?卡哥:但还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!

class Solution {
public:
    bool isValid(string s) {
        if(s.size()%2)return false;
        stack<char> st;
        for(auto a:s){
            if(a=='('){
                st.push(')');
            }else if (a=='['){
                st.push(']');
            }else if (a=='{'){
                st.push('}');
            }else if (st.empty() || st.top()!= a) return false; //遍历的时候如果空 右括号多;如果不等于 左括号多
            else st.pop(); //与栈顶元素一致,弹出
        }
        //左右括号数量一样但是不匹配
        return st.empty();
    }
};

还有一种看到别人做的很好的思路用map:遍历字符串如果遇到左括号就压入栈,如果遇到右括号则判断栈顶元素和map匹配是否相等或者判断栈是否为空,与上面的最后一个else if是类似的道理。

class Solution {
public:
    bool isValid(string s) {
        int n = s.size();
        if (n % 2 == 1) {
            return false;
        }

        unordered_map<char, char> pairs = {
            {')', '('},
            {']', '['},
            {'}', '{'}
        };
        stack<char> stk;
        for (char ch: s) {
            if (pairs.count(ch)) {
                if (stk.empty() || stk.top() != pairs[ch]) {
                    return false;
                }
                stk.pop();
            }
            else {
                stk.push(ch);
            }
        }
        return stk.empty();
    }
};

1047. 删除字符串中的所有相邻重复项

栈的经典应用。要知道栈为什么适合做这种类似于爱消除的操作,因为栈帮助我们记录了 遍历数组当前元素时候,前一个元素是什么。题目链接/文章讲解/视频讲解
思路: 遍历字符串,首先压入第一个字符到栈中,然后判断栈顶元素和当前遍历元素是否相同,相同便弹出,最后栈中的元素便是结果,但是卡在了不知道怎么输出字符串?因为遍历栈赋给新的字符串后是反的,看了答案真的对自己无语!字符串翻转!!!!
问题2:当堆栈 st 为空时,调用 st.top() 会导致未定义行为,因为你尝试访问一个不存在的元素。所以需要在访问 st.top() 之前检查堆栈是否为空!!!

class Solution {
public:
    string removeDuplicates(string s) {
        stack<char> st;
        string res;
        for (auto a : s) {
            if ( !st.empty() && st.top() == a) {
                st.pop();
            } else {
                st.push(a);
            }
        }
        while (!st.empty()) {
            res.push_back(st.top());
            st.pop();
        }
        reverse(res.begin(), res.end()); // 翻转字符串!!
        return res;
    }
};

卡哥思路2:学一下拿字符串直接作为栈。

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

学习时间

2024.6.15/2024.6.17

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值