首页 > 其他分享 >第十天|栈与队列| 232.用栈实现队列,225. 用队列实现栈,20. 有效的括号,1047. 删除字符串中的所有相邻重复项

第十天|栈与队列| 232.用栈实现队列,225. 用队列实现栈,20. 有效的括号,1047. 删除字符串中的所有相邻重复项

时间:2024-07-27 18:55:29浏览次数:18  
标签:20 第十天 队列 queue2 queue1 int return public

目录

232.用栈实现队列

225. 用队列实现栈

两个队列模拟栈

实现思路1:

实现思路2:

实现思路3:

一个队列模拟栈

实现思路1:

实现思路2:

实现思路3:

20. 有效的括号

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

方法1:使用Deque堆栈

方法2:用字符串直接当作栈

方法3:双指针


Day10学习到用来解决的经典题目类型:匹配相邻元素,删除相邻相同元素。

232.用栈实现队列

栈   : 先进后出

队列:先进先出

栈来模拟队列的行为,两个栈一个输入栈,一个输出栈就可以实现。

栈的初始化:Stack<Integer> st = new Stack<Integer>();

boolean empty(); 测试堆栈是否为空。

Object peek( ); 查看堆栈顶部的对象,但不从堆栈中移除它。

Object pop( ); 移除堆栈顶部的对象,并作为此函数的值返回该对象。

Object push(Object element); 把项压入堆栈顶部。

int search(Object element); 返回对象在堆栈中的位置,以 1 为基数。

push数据的时候,只要数据放进输入栈就好。

pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入),再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。

如何判断队列为空呢?如果进栈和出栈都为空的话,说明模拟的队列为空了。

在代码实现的时候,会发现pop() 和 peek()两个函数功能类似,可以先调用pop(),然后再把pop弹出的元素push进去。

class MyQueue {
    Stack<Integer> stackIn;
    Stack<Integer> stackOut;

    /** Initialize your data structure here. */
    public MyQueue() {
        stackIn = new Stack<>(); // 负责进栈
        stackOut = new Stack<>(); //负责出栈
    }

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

    /** Removes the element from in front of queue and returns that element. */
    public int pop() {
        dumpstackIn();
        return stackOut.pop();
    }

    /** Get the front element. */
    public int peek() {
        dumpstackIn();
        return stackOut.peek();
    }

    /** Returns whether the queue is empty. */
    public boolean empty() {
        return stackIn.isEmpty() && stackOut.isEmpty();
    }

    // 如果stackOut为空,那么将stackIn中的元素全部放到stackOut中
    private void dumpstackIn(){
        if (!stackOut.isEmpty()){return;}
        while (!stackIn.isEmpty()){
            stackOut.push(stackIn.pop());
        }
    }
}

225. 用队列实现栈

LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。

队列的初始化:Queue<Integer> queue = new LinkedList<>();

queue.offer(); 添加元素

queue.poll(); 返回第一个元素,并在队列中删除

queue.element(); 返回第一个元素

queue.peek(); 返回第一个元素

最终掌握一个队列模拟栈中的实现思路2或者实现思路3就可以了。

其他可当作小练习。

两个队列模拟栈

一个输入队列,一个输出队列,就可以模拟栈的功能吗?仔细想一下还真不行!

队列是先进先出的规则,把一个队列中的数据导入另一个队列中,数据的顺序并没有变,并没有变成先进后出的顺序。

所以用栈实现队列, 和用队列实现栈的思路还是不一样的,这取决于这两个数据结构的性质。

用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。

实现思路1:

在队列push的时候就利用2个队列将queue1的顺序排列为和栈一样的顺序,这样后续操作直接进行,不用更改了。

push的时候queue2作为辅助队列,即首先,将新元素 x 添加到 queue2 中。然后,将 queue1 中的所有元素逐个移到 queue2 中。这一步的作用是将新元素放在栈顶的位置,因为 queue2 中先放入了新元素 x。接着,我们交换 queue1queue2 的引用。交换后,queue1 中包含了所有的元素,其中 x 在最前面,这样就实现了栈的 LIFO(后进先出)性质。

通过这种方式,queue1 中总是保持栈的正确顺序,确保新元素总是位于栈顶。

    class MyStack {
        Queue<Integer> queue1; // 和栈中保持一样元素的队列
        Queue<Integer> queue2; // 辅助队列

        /**
         * Initialize your data structure here.
         */
        public MyStack() {
            queue1 = new LinkedList<>();
            queue2 = new LinkedList<>();
        }

        /** Push element x onto stack. */
        public void push(int x) {
            queue2.offer(x); // 先放在辅助队列中
            while (!queue1.isEmpty()){
                queue2.offer(queue1.poll());
            }
            Queue<Integer> queueTemp;
            queueTemp = queue1;
            queue1 = queue2;
            queue2 = queueTemp;// 最后交换queue1和queue2,将元素都放到queue1中
        }

        public int pop() {
            return queue1.poll(); // 因为queue1中的元素和栈中的保持一致,所以这个和下面两个的操作只看queue1即可
        }

        public int top() {
            return queue1.peek();
        }

        public boolean empty() {
            return queue1.isEmpty();
        }
    }

实现思路2:

和思路1类似,通过这种方式,queue1 中总是保持栈的正确顺序,确保新元素总是位于栈顶。

push的时候将queue2当作临时放置,更易理解。

    class MyStack {
        Queue<Integer> queue1; // q1作为主要的队列,其元素排列顺序和出栈顺序相同
        Queue<Integer> queue2; // q2仅作为临时放置

        /**
         * Initialize your data structure here.
         */
        public MyStack() {
            queue1 = new LinkedList<>();
            queue2 = new LinkedList<>();
        }

        /** Push element x onto stack. */
        //在加入元素时先将q1中的元素依次出栈压入q2,然后将新加入的元素压入q1,再将q2中的元素依次出栈压入q1
        public void push(int x) {
            while (queue1.size() > 0){
                queue2.add(queue1.poll());
            }
            queue1.add(x);
            while (queue2.size()>0){
                queue1.add(queue2.poll());
            }
        }

        public int pop() {
            return queue1.poll(); // 因为queue1中的元素和栈中的保持一致,所以这个和下面两个的操作只看queue1即可
        }

        public int top() {
            return queue1.peek();
        }

        public boolean empty() {
            return queue1.isEmpty();
        }
    }

实现思路3:

使用两个 Deque 实现

Java集合框架的Deque接口提供了双端队列(Deque)的功能。Deque 接口继承了 Queue 接口,所以 Queue 中的 add、poll、peek等效于 Deque 中的 addLast、pollFirst、peekFirst

Deque 的初始化:

Deque<Integer> queue1 = new ArrayDeque<>();

Deque<String> queue2 = new LinkedList<>();

在双端队列中,可以从前后插入和删除元素

    class MyStack {
        Deque<Integer> queue1; // queue1和栈中保持一样元素的队列
        Deque<Integer> queue2; // 辅助队列

        public MyStack() {
            queue1 = new ArrayDeque<>();
            queue2 = new ArrayDeque<>();
        }

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

        public int pop() {
            int size = queue1.size();
            size--;
            // 将 queue1 导入 queue2 ,但留下最后一个值
            while (size-- > 0) {
                queue2.addLast(queue1.peekFirst());
                queue1.pollFirst();
            }
            int res= queue1.pollFirst(); //res就是栈顶元素。
            // 将 queue2 对象的引用赋给了 queue1 ,此时 queue1,queue2 指向同一个队列
            queue1 = queue2;
            // 如果直接操作 queue2,queue1 也会受到影响,所以为 queue2 分配一个新的空间
            queue2 = new ArrayDeque<>();
            return res;

        }

        public int top() {
            return queue1.peekLast(); //由于在 push 方法中将新元素添加到队尾,所以 queue1 的最后一个元素始终是栈顶元素。
        }

        public boolean empty() {
            return queue1.isEmpty();
        }
    }

一个队列模拟栈

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

实现思路1:

简化两个队列实现的实现思路3。Deque

    class MyStack {
        // Deque 接口继承了 Queue 接口
        // 所以 Queue 中的 add、poll、peek等效于 Deque 中的 addLast、pollFirst、peekFirst
        Deque<Integer> queue1;

        public MyStack() {
            queue1 = new ArrayDeque<>();
        }

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

        public int pop() {
            int size = queue1.size();
            size--;
            // 将 queue1 导入 queue2 ,但留下最后一个值
            while (size-- > 0) {
                queue1.addLast(queue1.peekFirst());
                queue1.pollFirst();
            }
            int res= queue1.pollFirst(); //res就是栈顶元素。
            return res;

        }

        public int top() {
            return queue1.peekLast(); //由于在 push 方法中将新元素添加到队尾,所以 queue1 的最后一个元素始终是栈顶元素。
        }

        public boolean empty() {
            return queue1.isEmpty();
        }
    }

实现思路2:

使用一个 Queue 实现。

在队列push的时候就利用一个 Queue将queue1的顺序排列为和栈一样的顺序,这样后续操作直接进行,不用更改了。

    class MyStack {
        Queue<Integer> queue1;

        public MyStack() {
            queue1 = new LinkedList<>();
        }

        //每 offer 一个数(x)进来,都重新排列,把这个数(x)放到队列的队首
        public void push(int x) {
            queue1.offer(x);
            int size = queue1.size();
            //移动除了 x 的其它数
            while (size-->1){
                queue1.offer(queue1.poll());
            }
        }

        public int pop() {
            return queue1.poll();
        }

        public int top() {
            return queue1.peek();
        }

        public boolean empty() {
            return queue1.isEmpty();
        }
    }

实现思路3:

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

 class MyStack {
        Queue<Integer> queue1;

        public MyStack() {
            queue1 = new LinkedList<>();
        }


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

        public int pop() {
            rePosition();
            return queue1.poll();
        }

        public int top() {
            rePosition();
            int result = queue1.poll();
            queue1.add(result);
            return result;
        }

        public boolean empty() {
            return queue1.isEmpty();
        }
        public void rePosition(){
            int size = queue1.size();
            size--;
            while (size-->0){
                queue1.add(queue1.poll());
            }
        }
    }

20. 有效的括号

由于栈结构的特殊性,非常适合做对称匹配类的题目。

思路分析:

一共有3种不匹配的情况,即不属于有效的括号。

  1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
  2. 第二种情况,括号没有多余,但是括号的类型没有匹配上。
  3. 第三种情况,字符串里右方向的括号多余了,所以不匹配。

第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false

第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false

第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false

当字符串遍历完之后,栈是空的,就说明全都匹配了。

注意:在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!

    class Solution {
        public boolean isValid(String s) {
            Deque<Character> deque = new LinkedList<>();
            char ch;
            for (int i = 0; i < s.length(); i++) {
                ch = s.charAt(i);
                //碰到左括号,就把相应的右括号入栈
                if (ch == '(') {
                    deque.push(')');
                } else if (ch == '{') {
                    deque.push('}');
                } else if (ch == '[') {
                    deque.push(']');
                } else if (deque.isEmpty() || deque.peek() != ch) { //第三种情况和第二种情况
                    return false;
                } else { //如果是右括号判断是否和栈顶元素匹配,相配则弹出栈
                    deque.pop();
                }
            }
            //最后判断栈中元素是否匹配
            return deque.isEmpty(); //若deque.isEmpty()==false,则为第一种情况
        }
    }

具体的代码实现也很有意思,挺简洁的,最后一步return可参考。

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

思路:栈来存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。然后再去做对应的消除操作。然后从栈里弹出元素,由于弹出的元素是倒序的,所以再对字符串进行反转一下,就得到了最终的结果。

也可以拿字符串直接作为栈,这样省去了栈还要转为字符串的操作。

补充小tips:递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的

方法1:使用Deque堆栈

    class Solution {
        public String removeDuplicates(String s) {
            //ArrayDeque会比LinkedList在除了删除元素这一点外会快一点
            //参考:https://stackoverflow.com/questions/6163166/why-is-arraydeque-better-than-linkedlist
            ArrayDeque<Character> deque = new ArrayDeque<>();
            char ch;
            for (int i = 0; i < s.length(); i++) {
                ch = s.charAt(i);
                if (deque.isEmpty() || deque.peek() != ch) {
                    deque.push(ch);
                } else {
                    deque.pop();
                }
            }
            String str = "";
            //剩余的元素即为不重复的元素
            while (!deque.isEmpty()){
                str = deque.pop() + str;  //这里相当于直接做了倒序
            }
            return str;
        }
    }

方法2:用字符串直接当作栈

    class Solution {
        public String removeDuplicates(String s) {
//            方法2:用字符串当栈
            // 将 res 当做栈
            // 也可以用 StringBuilder 来修改字符串,速度更快
            // StringBuilder res = new StringBuilder();
            StringBuffer res = new StringBuffer();
            // top为 res 的长度
            int top = -1;
            for (int i = 0; i < s.length(); i++) {
                char c = s.charAt(i);
                // 当 top > 0,即栈中有字符时,当前字符如果和栈中字符相等,弹出栈顶字符,同时 top--
                if (top >= 0 && res.charAt(top) == c) {
                    res.deleteCharAt(top);
                    top--;
                } else { // 否则,将该字符 入栈,同时top++
                    res.append(c);
                    top++;
                }
            }
            return res.toString();
        }
    }

方法3:双指针

双指针的思路也很妙,具体见代码注释。

    class Solution {
        public String removeDuplicates(String s) {
//            方法3:双指针
            char[] ch = s.toCharArray();
            int fast = 0;
            int slow = 0;
            while (fast < s.length()) {
                // 直接用fast指针覆盖slow指针的值
                ch[slow] = ch[fast];
                // 遇到前后相同值的,就跳过,即slow指针后退一步,下次循环就可以直接被覆盖掉了
                if (slow > 0 && ch[slow] == ch[slow - 1]) {
                    slow--;
                } else {
                    slow++;
                }
                fast++;
            }
            return new String(ch, 0, slow);
        }
    }

第十天的总算是结束了,直冲Day11!

标签:20,第十天,队列,queue2,queue1,int,return,public
From: https://blog.csdn.net/qq_51726003/article/details/140735808

相关文章

  • 2024/07/27 每日一题
    LeetCode3106满足距离约束且字典序最小的字符串classSolution:defgetSmallestString(self,s:str,k:int)->str:ans=list(s);res=ord('a')fori,xinenumerate(map(ord,ans)):cnt=min(x-res,res+26-x)......
  • 洛谷P1042 [NOIP2003 普及组] 乒乓球
    题目链接:-P1042[NOIP2003普及组]乒乓球[NOIP2003普及组]乒乓球题目背景国际乒联现在主席沙拉拉自从上任以来就立志于推行一系列改革,以推动乒乓球运动在全球的普及。其中11分制改革引起了很大的争议,有一部分球员因为无法适应新规则只能选择退役。华华就是其中一位,他退役......
  • 算法训练 2024.7.27 17:25
    目录1.两数之和2.反转链表3.是否为有效的括号4.最长公共前缀5.合并两个有序数组6.岛屿的个数7.最小路径和8.三数之和9.计数质数10.字符串转换整数(atoi)1.两数之和题目:给定一个整数数组nums和一个目标值target,请你在该数组中找出和为目标值的那两个整......
  • Python数据预处理+正态性检验+异常值处理+Q-Q图-K-S检验+相关性分析(2024MathorCup A题
    #数据预处理#正态性检验、Q-Q图、箱线图、直方图、相关性分析#Q-Q图importnumpyasnpimportpandasaspdimportmatplotlib.pyplotaspltfromscipy.statsimportnormfromscipy.statsimportprobplota=pd.read_excel('附件1:小区基本信息.xlsx',engine='openpyxl'......
  • 2024暑假第四周总结
    数组容器,可以用来存储同种数据类型的多个值需要结合隐式转换考虑容器的类型和存储数据的类型保持一致数组的定义:格式一:数据类型[]数组名int[]array格式二:数据类型数组名[]intarray[]数组初始化:在内存中,为数组容器开辟空间,并将数据存入容器中的过程数组静态初......
  • 2024.7.22至2024.7.27周总结
    本周学习任务清单数据结构:树链剖分。解题思路:CDQ分治,整体二分。数论:费马小定理,素数筛法,欧拉定理,逆元,拓展欧几里得算法,中国剩余定理,Miller_Rabin素数检测,PollarRho分解质因数算法。多项式和生成函数:拉格朗日插值法,普通生成函数。线性代数:向量,线性组合,线性变换,线性,矩阵,行列......
  • 2024暑假集训测试13
    前言比赛链接。从来没见过交互题,T1狂CE不止心态炸了,后面的题也没打好,T2、T3简单题都不会了,所以为啥T4又放黑题。T1大众点评原题:AT_joisc2014_d。难点主要在交互,赛时琢磨了半场比赛终于搞明白是啥玩意儿了,可以将给定库当成压缩的一部分代码,可以调用里面的函数,输入......
  • 怎么判断电脑屏幕被监控?电脑被监控可以看到什么?丨2024超强科普!
    各位同仁,是不是正在怀疑自己的电脑被监控了?那么又该怎么盘点自己的电脑是不是正在被监控,假如真的被监控,老板又会看到什么内容呢?别急,且听我慢慢道来!一、电脑被监控的表现黑屏闪烁当电脑被监控时,屏幕可能会出现短暂的黑屏或频繁闪烁。这种情况多出现在电脑启动或打开特定程......
  • 2001-2022年上市公司企业资本结构动态调整数据集(含原始数据、计算代码、参考文献及最
    企业资本结构动态调整数据:优化财务杠杆与治理结构的关键指标企业资本结构动态调整是企业根据市场环境和自身发展需求,对债务和股权资本比例进行的有意识调整。这种调整有助于企业提高治理效率、增强市场竞争力,同时降低风险,确保长期稳定发展。资本结构动态调整的目的适应市场......
  • P2024 [NOI2001] 食物链
    原题链接题解关系具有矢量特性,因此可以带权并查集维护code#include<bits/stdc++.h>#definelllonglongusingnamespacestd;intfa[50006];intval[50006];intfinds(intnow){if(now==fa[now])returnnow;inttem=fa[now];fa[now]=finds(fa[now])......