代码随想录算法训练营:10/60

非科班学习算法day10 | LeetCode232:用栈实现队列 ,Leetcode225:用队列实现栈 ,Leetcode20:有效括号,LeetCode1047:删除字符串中的所有相邻项 

目录

介绍

一、基础概念补充:

1.c++中的栈

常用函数

2.c++中的队列

常用函数

二、LeetCode题目

1.LeetCode232: 用栈实现队列

题目解析

 2.Leetcode225:用队列实现栈 

题目解析

3.Leetcode20:有效括号

题目解析

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

题目解析

总结


介绍

包含LC的四道题目,还有相应概念的补充。暂时告别了复杂的字符串处理,来到了栈和队列的应用。这两者的特性都是一定的,常用的函数也就那几个,主要是控制怎么模拟出正确的过程。

相关图解和更多版本:

代码随想录 (programmercarl.com)https://programmercarl.com/#%E6%9C%AC%E7%AB%99%E8%83%8C%E6%99%AF


一、基础概念补充:

1.c++中的栈

C++中的栈(stack)是一种后进先出(Last In, First Out, LIFO)的数据结构,它只允许在一端进行插入和删除操作。这一端称为栈顶,另一端称为栈底。C++标准库提供了<stack>头文件,其中定义了std::stack容器适配器来实现栈的功能。std::stack本身不是一个容器,而是基于其他容器(如std::vectorstd::dequestd::list,默认是std::deque)实现的。

常用函数

  1. 构造函数

    • stack(); // 默认构造函数,创建一个空栈。
    • explicit stack(const Container& cont); // 使用给定的容器cont来初始化栈。
  2. 元素访问

    • top(); // 返回栈顶元素的引用,但不移除它。如果栈为空,则行为未定义。
  3. 容量

    • 由于std::stack是基于其他容器实现,直接访问容量相关函数(如大小size)通常需要通过底层容器的接口间接实现,标准库中没有直接提供栈容量相关的成员函数。
  4. 修改器

    • push(const value_type& x); // 在栈顶添加一个元素x。
    • push(value_type&& x); // C++11起,支持右值引用,高效地将x推入栈顶。
    • pop(); // 移除栈顶元素,无返回值。
  5. 状态检查

    • empty(); // 如果栈为空,返回true;否则,返回false。 // 注意:没有直接提供size()函数,但可以通过底层容器的size()方法间接获取栈的大小。

2.c++中的队列

C++中的队列(queue)是一种先进先出(First In, First Out, FIFO)的数据结构,它允许在队尾插入元素,在队头删除元素。C++标准库在<queue>头文件中提供了std::queue容器适配器来实现队列功能,同样地,std::queue也是基于其他容器(默认是std::deque)实现的,并不直接管理内存。

常用函数

  1. 构造函数

    • queue(); // 默认构造函数,创建一个空队列。
    • explicit queue(const Container& cont); // 使用给定的容器cont来初始化队列。
  2. 元素访问

    • front(); // 返回队头元素的引用,但不移除它。如果队列为空,则行为未定义。
    • back(); // 返回队尾元素的引用,不移除它。如果队列为空,则行为未定义。
  3. 容量

    • 同栈一样,直接的容量查询不是std::queue的标准成员函数,但可通过底层容器接口间接实现。
  4. 修改器

    • push(const value_type& x); // 在队尾添加一个元素x。
    • push(value_type&& x); // C++11起,支持右值引用,高效地将x加入队尾。
    • pop(); // 移除队头元素,无返回值。
  5. 状态检查

    • empty(); // 如果队列为空,返回true;否则,返回false。 // 注意:没有直接提供size()函数,但可以通过底层容器的size()方法间接获取队列的大小。

        

二、LeetCode题目

1.LeetCode232: 用栈实现队列

题目链接:232. 用栈实现队列 - 力扣(LeetCode)

题目解析

       因为栈是单个开口的,所以要用栈实现队列,就要借助两个栈。这两个栈,一个用来模拟进栈,另一个用来模拟出栈。

 c++代码如下:

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 result = this->pop();
        stOut.push(result);
        return result;
    }
    
    bool empty() 
    {
        if(stIn.empty() && stOut.empty())
        {
            return true;
        }
        return false;
    }
};

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

注意点1:出队列的模拟是借助出栈来实现,不过显然,队列和栈的进出顺序不一样,所以,将进栈的元素依次弹出并添加到出栈当中。

注意点2:获取队列头的元素和弹出队列头元素的操作很像,只不过就是弹出和不弹出的区别,所以这里调用了之前写的函数,然后再进一步操作,很大程度上缩减了代码,并且更利于维护。

 2.Leetcode225:用队列实现栈 

题目链接:225. 用队列实现栈 - 力扣(LeetCode)

题目解析

       借用和上一道题相似的思路,可以建立两个队列来模拟栈

 C++代码如下: 

class MyStack {
public:
    //创建两个队列
    queue<int>que1;
    queue<int>que2;

    MyStack() {

    }
    
    void push(int x) 
    {
        que1.push(x);
    }
    
    int pop() 
    {
        int size = que1.size();
        while(size > 1)
        {
            que2.push(que1.front());
            que1.pop();
            size--;
        }

        int result = que1.front();
        que1.pop();

        while(!que2.empty())
        {
            que1.push(que2.front());
            que2.pop();
        }

        return result;
    }
    
    int top() 
    {
        return que1.back();
    }
    
    bool empty() 
    {
        return que1.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();
 */

3.Leetcode20:有效括号

题目链接:20. 有效的括号 - 力扣(LeetCode)

题目解析

       也是借助栈,采用好用的方法,会大大减少条件判断。问题就是,栈中的元素怎么处理?这里是采用遇到左括号,进栈对应的右括号;遇到右括号,弹栈。监控这个过程,如果发生了异常,就表示不成立。

C++代码如下:

class Solution {
public:
    bool isValid(string s) 
    {
        //剪枝
        if(s.size() % 2 != 0)return false;

        //建立栈
        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() || s[i] != st.top()) return false;
            //()]]
            else st.pop();
        }

        return st.empty();
    }
};

注意点1:这里建立的栈存放的类型是字符,在c++中字符和字符串并不一样。

注意点2:我一开始写的条件是s[i] != st.top() || st.empty(),这样会造成如下问题:

这里有一个潜在的逻辑问题。理论上,在检查 s[i] != st.top() 之前应该先确认栈非空。但由于逻辑或(||)的短路特性,实际执行时:
如果先计算 s[i] != st.top(),在某些编译器/环境可能尝试访问空栈的顶部,这是未定义行为,可能引发运行时错误。
但实际上,由于 || 操作符的特性,计算机会直接跳过 s[i] != st.top() 的检查,直接评估 st.empty(),该部分为真(true),导致整个表达式结果为 true,正确地反映出栈为空的情况,而不实际执行对空栈顶的访问。
尽管在大多数现代编译器中,由于短路特性,第三种情况通常不会出现问题,但最佳实践仍然是先检查栈是否为空,以避免潜在的未定义行为,这也是为什么推荐使用 st.empty() || s[i] != st.top() 的原因。

总结一下就是,在要访问可能为空的容器的元素之前,最好先进行检查,&&同理。

注意点3:把这种问题理解成消除问题,这也是遇到左边括号,进栈相应的右边括号的原因,借助栈,减少了大量的条件判断,底层的原因就是因为,需要处理的元素之间有相应关系。

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

题目链接:1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)

题目解析

       根据对数组、字符串的了解,如果选择直接判断元素是否相同,然后对其原地操作,前向遍历会影响索引,所以非常难使用;后向遍历也要判断很多次,因为题目中提到了需要反复操作直到没有相邻重复的。

        其实这个过程很像消消乐,遇到这种问题,最好的处理就是使用栈。

C++代码如下:

class Solution {
public:
    string removeDuplicates(string s) 
    {
        //建立栈
        stack<char> st;

        //进栈
        for(auto ch : s)
        {
            if(!st.empty() && ch == st.top())
            {
                st.pop();
            }
            else
            {
                st.push(ch);
            }
        }

        string new_s = "";
        while(!st.empty())
        {
            new_s += st.top();
            st.pop();
        }

        reverse(new_s.begin(), new_s.end());
        return new_s;
    }
};

注意点1:和上一道题的处理相似,或者说栈的处理都类似:看准当前栈顶的元素,看准当前需要处理的元素(未进栈),根据这两者做出判断

注意点2:维护的是栈的状态。

总结


栈多画一画图,还是比较好理解 0v0 ,打卡第10天,坚持!!!

  • 23
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值