首页 > 编程语言 >代码随想录算法训练营第十天 | 232.用栈实现队列+225. 用队列实现栈+20. 有效的括号+1047. 删除字符串中的所有相邻重复项

代码随想录算法训练营第十天 | 232.用栈实现队列+225. 用队列实现栈+20. 有效的括号+1047. 删除字符串中的所有相邻重复项

时间:2024-11-11 12:49:46浏览次数:3  
标签:第十天 队列 随想录 pop que1 push 字符串 empty

加入训练营有点晚,没跟上任务就开始有些偷懒没有真的认真开始,从第十天开始下决心认真完成每天任务还有博客记录学习过程。

栈与队列理论基础

首先是学习了栈和队列的理论基础,队列  先进先出,栈  先进后出。

 以底层容器完成其所有的工作,且对外接口统一,有.push,.pop等,不提供走访,不提供迭代器。


  1. 本身是一种抽象的数据结构,它并不直接去处理元素的存储细节等具体事务。而是借助如前面提到的 vector、deque、list 等底层容器来实际完成这些基础工作。无论栈选择哪种底层容器(vector、deque 还是 list)来实现,它对使用者呈现出的操作接口都是统一的。

  2. 不提供走访

    栈的设计初衷是遵循后进先出原则来管理数据,它不是为了让用户能够随意遍历其中的所有元素而存在的。

    与数组(比如 vector,如果把它单纯作为数组看待)或者链表(list)等数据结构不同,这些数据结构通常会提供一些方式让用户可以逐个访问其中的每个元素,比如通过索引(对于数组)或者沿着指针(对于链表)去走访每个节点。但栈一般不提供这样的功能,因为走访元素的操作不符合栈的后进先出逻辑,而且可能会破坏栈数据结构的特性。如果允许随意走访,用户可能会在不遵循后进先出原则的情况下访问和操作元素,从而导致栈失去其原本应有的功能和数据结构的一致性。

  3. 不提供迭代器
    迭代器是一种用于遍历容器中元素的工具,很多容器(如 vector、list 等)都提供迭代器来方便用户按照一定顺序访问容器内的元素。
    然而栈不提供迭代器,原因和不提供走访功能类似。栈重点在于实现后进先出的操作,如入栈、出栈等特定功能,而不是为了让用户能够像遍历数组或链表那样全面地去查看和处理其中的每个元素。提供迭代器可能会诱导用户去做一些不符合栈逻辑的操作,比如在迭代过程中随意插入或删除元素,这会破坏栈的后进先出特性以及整体的结构完整性。所以为了保持栈的纯粹性和特性,一般不提供迭代器。

232.用栈实现队列

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

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

使用两个栈实现先入先出队列,关键点在于如何用后入先出的特性来实现先入先出的特性。
这点也很好理解,将输入序列倒序之后的先入先出即为后入先出,为了实现这个有以下代码:

class MyQueue {
public:
    stack<int> stIn;
    stack<int> stOut;
    /** Initialize your data structure here. */
    MyQueue() {

    }
    /** Push element x to the back of queue. */
    void push(int x) {
        stIn.push(x);
    }

    /** Removes the element from in front of queue and returns that element. */
    int pop() {
        // 只有当stOut为空的时候,再从stIn里导入数据(导入stIn全部数据)
        if (stOut.empty()) {
            // 从stIn导入数据直到stIn为空
            while(!stIn.empty()) {
                stOut.push(stIn.top());//.top()获取栈顶元素但不移除
                stIn.pop();//从栈顶移除
            }
        }
        int result = stOut.top();
        stOut.pop();
        return result;
    }

    /** Get the front element. */
    int peek() {
        int res = this->pop(); // 直接使用已有的pop函数
        stOut.push(res); // 因为pop函数弹出了元素res,所以再添加回去
        return res;//直接使用已有定义的函数,避免资源浪费(也是为什么不用.top的原因?)
    }

    /** Returns whether the queue is empty. */
    bool empty() {
        return stIn.empty() && stOut.empty();
    }
};

225. 用队列实现栈

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

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
    注意:
  • 你只能使用队列的标准操作 —— 也就是 push to backpeek/pop from frontsize 和 is empty 这些操作。
  • 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

 用两个队列实现一个后入先出(LIFO)的栈,队列的特点是先入先出,如何做到后入先出呢,这里的思路是将“先入”的队列先让他们去旁边等一等,也就是复制到另一个队列。具体代码如下:
 

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());//.front() 获取队列1队首值,排入队列2
            que1.pop();//将队首值移除
        }

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

    /** Get the top element.
     ** Can not use back() directly.
     */
    int top(){//操作与pop相同,区别在于保留了队尾元素,一起排入队列2
        int size = que1.size();
        size--;
        while (size--){
            // 将que1 导入que2,但要留下最后一个元素
            que2.push(que1.front());
            que1.pop();
        }

        int result = que1.front(); // 留下的最后一个元素就是要回返的值
        que2.push(que1.front());   // 获取值后将最后一个元素也加入que2中,保持原本的结构不变
        que1.pop();

        que1 = que2; // 再将que2赋值给que1
        while (!que2.empty()){
            // 清空que2
            que2.pop();
        }
        return result;
    }

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

20. 有效的括号

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

 卡尔:括号匹配是使用栈解决的经典问题。

这里有三种不匹配的情况:

  1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。 

    括号匹配1

  2. 第二种情况,括号没有多余,但是 括号的类型没有匹配上。 

    括号匹配2

  3. 第三种情况,字符串里右方向的括号多余了,所以不匹配。 

    括号匹配3

卡尔制作的匹配动画如下:

20.有效括号

动画看起来非常直观了,简要解释:将左括号依次填入栈,在遇到对应右括号时从栈中移除。

在代码具体实现过程中卡尔提到一个小技巧,就像动画展示的,遍历左括号时向栈中压入相应右括号,这样在匹配括号时,只用判断其是否与右括号相等。

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

 (题目中输入字符串只有括号所以相对较为简单,小菜鸡的我遇到含有别的字符的可能还是不会写)

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

给出由小写字母组成的字符串 s重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 s 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

 这题的解题思路:反复执行相邻重复项删除,实际上和20中的括号匹配思路相似,我们可以将字符串遍历压入栈中,首先比对其与前一位,也就是栈顶元素,是否相等,如相等,将栈顶移除,接下来比对下一位,如不相等,将其压入栈顶,直至字符串末尾。如动画所示:

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

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;

    }
};

 也可以直接将字符串作为栈。(个人理解:将字符串当作栈进行操作)

push_backpop_back是 C++ 中string类用于处理字符串末尾字符的成员函数:

push_back:用于在字符串末尾添加一个字符,会使字符串长度增加 1,示例中可将指定字符添加到已有字符串末尾。

pop_back:用于移除字符串末尾的一个字符,会使字符串长度减 1,能将已有字符串的最后一个字符删除。
这两个函数在字符串的动态处理、类似栈操作等场景中较为常用。

class Solution {
public:
    // 函数 removeDuplicates 用于从输入字符串S中移除相邻的重复字符
    string removeDuplicates(string S) {
        // 用于存储处理后的结果字符串
        string result;

        // 遍历输入字符串S中的每个字符
        for (char s : S) {
            // 如果结果字符串为空,或者当前字符与结果字符串的最后一个字符不相等
            if (result.empty() || result.back()!= s) {
                // 将当前字符添加到结果字符串中
                result.push_back(s);
            } else {
                // 如果当前字符与结果字符串的最后一个字符相等,说明出现了相邻重复字符
                // 则移除结果字符串的最后一个字符,以达到移除相邻重复字符的目的
                result.pop_back();
            }
        }

        // 返回处理后的结果字符串
        return result;
    }
};

 总结:

Day10题目相对比较简单,理解数据结构中栈和队列是重点。

标签:第十天,队列,随想录,pop,que1,push,字符串,empty
From: https://blog.csdn.net/qq_53853175/article/details/143668367

相关文章

  • 代码随想录——二叉树-12.平衡二叉树
    自顶向下递归(前序遍历)这种方法是一开始想到的,虽然ac了但是对于它的本质却不清不楚,不知道时间复杂度,不知道属于前序遍历。思路首先得到root节点的左右子树的深度(左右),若深度差绝对值大于1(中),则root为根的树不是平衡二叉树;否则继续递归root的左右子树,其左右子树都是平衡二叉树......
  • 代码随想录——二叉树-11.完全二叉树的节点个数
    思路一、层序遍历,时间复杂度O(n)二、利用完全二叉树性质,时间复杂度O(logn*logn)(小于O(n))完全二叉树性质:若树深度为h,则前h-1层节点都达到最大值。第h层节点都集中在最左侧的位置完全二叉树要么1.是满二叉树2.最后一层没满满二叉树计算节点数太方便了,直接用公式2^h-1。......
  • 代码随想录之滑动窗口、Java日期api、集合(11.4-11.11)
    代码1、长度最小的子数组⭐使用滑动窗口的思想,外层循环控制结束位置j,内层循环控制起始位置i,看似是双层循环,但时间复杂度是o(2n)。 2、水果成篮自己想法:使用backet1和backet2表示篮子1和篮子2;使用backet1Account和backet2Account分别表示两个篮子里水果的数量,内层循环将i指针......
  • 10. 基于 Redis 实现消息队列
    消息队列在分布式系统中非常重要,能够有效解耦系统的各个模块,提供异步处理能力和缓冲能力。Redis作为一个高性能的内存数据库,除了缓存和持久化存储,它还能充当轻量级的消息队列。使用Redis处理消息队列有助于提高系统的吞吐量和可扩展性。一、使用场景消息队列的应用场景......
  • (代码随想录)132. 分割回文串 II(动态规划)
    132.分割回文串II这一题直接将我打回cv工程师的原型除了dp还要定义一个辅助数组,用于表示i区间到j区间是否为回文串. 动规五部曲1.确定dp含义dp[i]表示0到i之间的字符串需要切割的最小次数2.确定递推公式第一种就是0到i之间直接就是一个回文串,那么直接dp[i]=0......
  • (代码随想录)leetcode300. 最长递增子序列
    自己还是写不出来[笑哭]思路错了,自己死要去只遍历一遍代码随想录答案:classSolution{public:intlengthOfLIS(vector<int>&nums){if(nums.size()<=1)returnnums.size();vector<int>dp(nums.size(),1);//所有元素都是1长度//dp[i]......
  • 单调队列笔记
    单调队列笔记双端队列deque维护一个严格单调变化的组,可以称为一个单调队列单调队列因为可以直接对组的两端进行操作,所以可以有效的降低时间复杂度用单调队列来解决问题,一般是需要得到的某个范围内的最小值或最大值这里以一道经典的单调队列的题目为例:题目描述有一个长为\(......
  • 栈和队列(原理、代码实现、例题)
    一、栈1.概念栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(LastInFirstOut)的原则。压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。出栈:栈的删除操作叫做......
  • 代码随想录算法训练营第19天|235. 二叉搜索树的最近公共祖先 ,701.二叉搜索树中的插入
    235.二叉搜索树的最近公共祖先文章链接:https://programmercarl.com/0235.二叉搜索树的最近公共祖先.html题目链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/思路:利用二叉搜索树的特性,当第一次遇到在[p,q]区间或[q,p]区间的元素的节点,则......
  • 代码随想录算法训练营第18天| 530.二叉搜索树的最小绝对差, 501.二叉搜索树中的众数 , 2
    530.二叉搜索树的最小绝对差文章链接:https://programmercarl.com/0530.二叉搜索树的最小绝对差.html视频链接:https://www.bilibili.com/video/BV1DD4y11779/?vd_source=6cb513d59bf1f73f86d4225e9803d47b题目链接:https://leetcode.cn/problems/minimum-absolute-difference-in......