代码随想录算法训练营第十天 | LeetCode232.用栈实现队列、LeetCode225.用队列实现栈、LeetCode20.有效的括号、LeetCode1047.删除字符串中的所有相邻重复项

LeetCode232.用栈实现队列

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

实现 MyQueue 类:

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

说明:

  • 只能 使用标准的栈操作 —— 也就是只有 push to toppeek/pop from topsize, 和 is empty 操作是合法的。
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

思路:想要用栈实现队列,首先要清楚栈和队列的各自工作方式。

栈是后进先出的,队列是先进先出的。于是想要用两个栈来模拟队列,可以采用下面的方法。

首先两个栈一个为进栈,一个为出栈,进出是因为有元素的push以及pop操作。push操作简单,直接将元素push入进栈即可,但是对于出栈,当要删除元素时,如果此时出栈为空,则需要将进栈的所有已入栈元素依次放入出栈中,这样就能在出栈中按照队列先进先出的原则进行。对于返回头部元素,其实与pop函数操作的逻辑很像,所以直接复用pop函数即可,注意需要将已删除元素加上去保持原状。而empty函数的判断则是看两个模拟队列的栈是否同时为空,同时为空则为空,否则不为空。

    stack<int> stIn;
    stack<int> stOut;//设置两个栈模拟过程
    MyQueue() {
        //这里可以自定义一些属性
    }
    
    void push(int x) {
        stIn.push(x);
    }
    
    int pop() {
        if(stOut.empty()){
            while(!stIn.empty()){
                int tmp = stIn.top();
                stIn.pop();
                stOut.push(tmp);
            }
        }
        int val = stOut.top();
        stOut.pop();
        return val;
    }
    
    int peek() {
        int val = this -> pop();//这里复用了之前已经定义好的pop函数
        stOut.push(val);//上面的获取元素操作将元素弹出来了,但是本身peek函数是查看队列的第一个元素,因此需要将其加回
        return val;
    }
    
    bool empty() {
        return (stIn.empty() && stOut.empty()); 
    }

时间复杂度:pop函数和peek函数是O(n),push函数和empty函数是O(1)

空间复杂度:O(n)

LeetCode225.用队列实现栈

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

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false

注意:

  • 你只能使用队列的标准操作 —— 也就是 push to backpeek/pop from frontsize 和 is empty 这些操作。
  • 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

 

思路:对于使用队列来模拟栈,有两种方法,一种是使用题目中所说的两个队列来模拟,还有一种是使用一个队列来模拟,但是总体上来说,两者的逻辑是统一的。

1、两个队列模拟栈

使用两个队列模拟栈,此时不再分为进队列和出队列,而是主队列和辅助队列。

主要还是对pop函数的实现,当出现pop删除元素时,将主队列中的不包含最后一个元素的所有元素依次从头部开始放到辅助队列里,这样当主队列剩下最后一个元素时,即为pop需要删除的元素,删除完成后,将辅助队列中的元素重新放入主队列,对辅助队列中的元素进行删除,即完成了栈顶元素的pop操作。

    queue<int> que1;
    queue<int> que2; //设置模拟的两个队列
    MyStack() {
        //这里可以自定义一些属性
    }
    
    void push(int x) {
        que1.push(x);
    }
    
    int pop() {
        int size = que1.size();
        size --; 
        while(size --){
            que2.push(que1.front());
            que1.pop(); //将除了最后一个元素之外的其他元素放入que2中
        }

        int val = que1.front();//获得了栈顶元素
        que1.pop();

        que1 = que2; //将que1恢复剩余元素状态

        while(!que2.empty()){
            que2.pop(); //清空辅助数组que2
        }
        return val;
    }
    
    int top() {
        int val = this -> pop();//处理逻辑相似,因此复用了pop函数
        que1.push(val);//因为只是查询,所以需要将删除的元素加回去
        return val;

        //直接写 return que1.back(); 也能过
    }
    
    bool empty() {
        return que1.empty();

    }

时间复杂度:pop函数和top函数是O(n),push函数和empty函数是O(1)

空间复杂度:O(n)

2、一个队列模拟栈

使用一个队列模拟栈,主要还是pop函数的实现。

当进行pop操作时,将除了最后一个元素的前面其他元素依次重新放入队列,这样就会使得之后的队头元素即为所模拟的栈的栈顶元素,获得其值并且将其pop点,即将栈顶元素删除,当继续进行pop操作时,重复上面的操作即可。

    queue<int> que;
    MyStack() {
        //这里可以自定义一些属性
    }
    
    void push(int x) {
        que.push(x);
    }
    
    int pop() {
        int size = que.size();
        size --; 
        while(size --){
            que.push(que.front());
            que.pop(); //将除了最后一个元素之外的其他元素重新放入que中
        }

        int val = que.front();//获得了对应的栈顶元素
        que.pop();

        return val;
    }
    
    int top() {
        int val = this -> pop();//处理逻辑相似,因此复用了pop函数
        que.push(val);//因为只是查询,所以需要将删除的元素加回去
        return val;

        //return que.back(); //直接写也能过
    }
    
    bool empty() {
        return que.empty();

    }

时间复杂度:pop函数和top函数是O(n),push函数和empty函数是O(1)

空间复杂度:O(n)

LeetCode20.有效的括号

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

有效字符串需满足:

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

思路 :栈是应用于匹配问题的最好解决办法。这里提供两种方法,一种是辅助数组法,一种是情况列举法。

1、辅助数组法

根据题意,将括号放入一个map中,键key为括号,值为某个数字,思路呢就是遍历字符串,当栈不为空时,尝试将栈顶元素和所遍历的元素的map值相加,如果等于0,那么说明它们是配对的,但是这还不能说明它们顺序是正确的(也就是左括号出现在右括号之前),因为我之前将所有的左括号的map值设为小于0,所以此时判断栈顶元素的map值是否小于0,如果是,匹配成功,继续遍历,如此循环。当遍历完后,如果说此时栈为空,说明所有括号全部匹配成功,于是返回true;反之,如果存在元素,不为空,说明还有一些括号没匹配上,则返回false。

    bool isValid(string s) {
        stack<char> st;
        unordered_map<char, int> mp;
        mp['('] = -1;
        mp[')'] = 1;
        mp['{'] = -2;
        mp['}'] = 2;
        mp['['] = -3;
        mp[']'] = 3; //将配对的括号分别设置对应的值
        for(int i = 0; i < s.size(); i ++){
            if(!st.empty()){
                if(mp[s[i]] + mp[st.top()] == 0 && mp[st.top()] < 0){
                    //当mp对应值相等时,说明能配对
                    //当栈中的括号mp值小于0时,说明是左括号,那么相对的,进来的就是右括号,
                    //由此才能在真正意义上配对
                    st.pop();
                    continue;
                }
            }
            st.push(s[i]);
        }
        if(st.empty()) return true; 
        //当栈st为空时,说明括号全部配对成功,返回true
        //否则不为空的时候返回false
        return false;

时间复杂度:O(n)

空间复杂度:O(n)

2、情况列举法

其实仔细分析一下所有匹配失败的情况,主要有三种:

1、右括号多余或者右括号出现在了左括号之前;

2、括号不匹配;

3、左括号多余。

此题我们采用了一些技巧,当遍历时为左括号时,全部换成其配对的右括号入栈,这样能够方便进行后续操作。

当第一种情况发生时,会使得遍历到该元素时,栈中已经为空,此时直接返回false即可;当第二种情况发生时,即所遍历的该元素与栈顶的元素不相等,直接返回false即可;当第三种情况发生时,是在遍历结束过后,当栈里面仍然还有元素,不为空时,说明多余的左括号没右括号匹配,此时也是直接返回false。当然很明显,当循环过程中没有返回false,循环自然结束,栈为空时,此时就认为各个括号都匹配成功了,直接返回true。

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(']');
            }else if(st.empty() || s[i] != st.top()){
                //当还有元素但是栈为空时,说明右括号多余了或者说是先于匹配的左括号出来了;
                //当还有元素但是与栈顶元素不匹配时,说明此时括号不相匹
                return false;
            }else if(s[i] == st.top()){
                st.pop();
            }
        }
        if(st.empty()) return true; 
        return false;//当循环结束,栈里面还有元素时,说明左括号多余了
    }

时间复杂度:O(n)

空间复杂度:O(n)

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

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

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

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

思路:当遇到需要对之前已经遍历过的元素进行判断的时候,栈也能够提供很好的解决方案。

因为栈是一个先进后出的数据结构,如果将刚遍历的元素放入栈,那么该元素在栈顶,方便查找,方便比较。

于是这道题的思路就很清晰了,循环遍历字符串s,当栈不为空时,对所遍历的元素与栈顶元素比较,如果元素相等,那么就是相邻的重复项,于是需要将栈顶元素删除,然后继续对后面的元素进行遍历,如果不是相邻重复项则将其压入栈中,如此循环。

当循环结束后,栈中元素即为非相邻重复项,此时用一个string字符串将里面的元素统计出来即可,但是需要注意字符的顺序,因为从栈里面导出必然会是倒叙,所以需要进行顺序的一个置换,之后返回即可。下面有两种导出的方式。

1、将字符加到result前面

string removeDuplicates(string s) {
        stack<char> st;
        for(int i = 0; i < s.size(); i ++){
            if(!st.empty()){
                if(s[i] == st.top()){
                    //当栈不为空的时候,此时如果s[i]与栈顶元素相等,
                    //那么就依据题意将其去除
                    st.pop();
                    continue;
                }
            }
            st.push(s[i]);
        }
        string result;
        while(!st.empty()){
            char tmp = st.top();
            //注意栈里面的元素如果是从栈顶开始输出
            //和原字符串的顺序是反的,所以这里是将元素加在前面
            result = tmp + result;
            st.pop();
        }
        return result;
    }

时间复杂度:O(n)

空间复杂度:O(n)

2、使用reverse函数

string removeDuplicates(string s) {
        stack<char> st;
        for(int i = 0; i < s.size(); i ++){
            if(!st.empty()){
                if(s[i] == st.top()){
                    //当栈不为空的时候,此时如果s[i]与栈顶元素相等,
                    //那么就依据题意将其去除
                    st.pop();
                    continue;
                }
            }
            st.push(s[i]);
        }
        string result;
        while(!st.empty()){
            //注意栈里面的元素如果是从栈顶开始输出,是逆序的
            result += st.top();
            st.pop();
        }
        reverse(result.begin(), result.end());//将其倒置
        return result;
    }

时间复杂度:O(n)

空间复杂度:O(n)

当然这里还有一种方法,那就是将string自身作为栈,这样就能减少创建栈所需的空间复杂度,如下图所示。

string removeDuplicates(string s) {
        string result;
        for(int i = 0; i < s.size(); i ++){
            if(!result.empty()){
                if(s[i] == result.back()){
                    result.pop_back();
                    continue;
                }
            }
            result.push_back(s[i]);
        }
        return result;
    }

时间复杂度:O(n)

空间复杂度:O(1)(result作为返回值是不计入空间复杂度的)

感谢你的阅读,希望我的文章能够给你帮助,如果有帮助,麻烦点赞加收藏,或者点点关注,非常感谢。

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二天的算法训练营主要涵盖了Leetcode题目的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口的元素的和满足题目要求。具体实现代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二天的算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理解和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值