力扣刷题Day9&10

栈与队列理论基础

java中的栈

参考资料:[Day10] 栈与队列理论基础、leetcode 232. 用栈实现队列 225. 用队列实现栈 · 语雀

Stack类

Stack类表示对象的后进先出(LIFO)堆栈。它使用五个操作扩展了Vector类,允许将vector视为stack。提供了通常的push和pop操作,以及一种查看堆栈顶部项的方法,一种测试堆栈是否为空的方法,以及一个在堆栈中搜索项并发现它离顶部有多远的方法。

首次创建堆栈时,它不包含任何项,即空栈。

Deque接口及其实现提供了一组更完整、更一致的后进先出堆栈操作,应该优先使用该类。例如:Deque<Integer>stack=new ArrayDeque<Integer>()

  1. Java的Stack类在java.util包中
  2. Stack类继承了Vector,而Vector类的底层实现是用数组实现的,那么Stack对象中存储的数据也是存储在数组中的

五个方法

push(E item)

将元素添加到栈的顶部,源码调用的是Vector的addElemnt方法,将添加的元素放在Vector末尾。注意调用push时,会将添加的元素返回

peek()

查看此堆栈顶部的对象,而不将其从堆栈中移除。返回值为该堆栈顶部的对象(the last item of the Vector object,Vector对象的最后一项)。如果栈为空,则抛出异常EmptyStack Exception

pop()

移除此堆栈顶部的对象,并将该对象作为此函数的值返回(the last item of the Vector object,Vector对象的最后一项)。如果栈为空,则抛出异常EmptyStackException

可以看到pop()是基于peek和removeElementAt实现的 

empty()

测试此堆栈是否为空。如果为空返回true,否则返回false

search(Object o)

返回对象在此堆栈上的基于1的位置,如果对象o是该stack中的一项,则此方法返回距离最接近栈顶部的对象的堆栈顶部的距离,堆栈上最顶端的项被认为距离为1。如果对象o不在该stack则返回-1

Java中的队列

Queue接口

Queue接口与List、Set同一级别,都是继承了Collection接口。

-------| Collection 单例集合的根接口。

------------| List 如果是实现了List接口的集合类,具备的特点: 有序,可重复。

------------| Set 如果是实现了Set接口的集合类,具备特点: 无序,不可重复。

------------| Queue 如果是实现了Queue接口的集合类,具备特点: 模拟队列存储结构,有序,先入先出

Queue的六个方法

队列是一种数据结构,它有两个基本操作:在队列尾部加入一个元素,和从队列头部移除一个元素。

Queue是java中实现队列的接口,它总共只有6个方法,我们一般只用其中3个就可以了。

插入操作的后一种形式(返回特殊值)是用于专门为有容量限制的 Queue 实现设计的;在大多数实现中,插入操作不会失败。

Queue的6个方法分类:

压入元素(添加):add()、offer()

相同:未超出容量,从队尾压入元素,返回压入的那个元素。

区别:在超出容量(队列满)时,add()方法会抛出异常,offer()返回false

弹出元素(删除):remove()、poll()

相同:容量大于0的时候,删除并返回队头被删除的那个元素。

区别:在容量为0(空队列)的时候,remove()会抛出异常,poll()返回false

获取队头元素(不删除):element()、peek()

相同:容量大于0的时候,都返回队头元素,但是不删除。

区别:容量为0(空队列)的时候,element()会抛出异常,peek()返回null

 Queue的子接口

三个子接口:BlockingDeque 、 BlockingQueue 、Deque

与 List 接口不同,此接口不支持通过索引访问元素

阻塞队列:

1、理解

阻塞队列是一个支持两个附加操作的队列,即在队列为满时,存储元素的线程会等待队列可用,当队列为空时,获取元素的线程会等待队列为非空。

2Java中的阻塞队列

阻塞队列接口有BlockingQueueBlockingDeque两个,其中后者是双向队列。

双端队列(Deque):一个线性 collection,支持在两端插入和移除元素。此接口既支持有容量限制的双端队列,也支持没有固定大小限制的双端队列

此接口定义 在双端队列两端访问元素的方法,提供插入、移除和检查元素的方法。

Deque接口扩展了 Queue 接口。在将双端队列用作队列时,将得到 FIFO(先进先出)行为。将元素添加到双端队列的末尾,从双端队列的开头移除元素,从 Queue 接口继承的方法完全等效于 Deque 方法。

双端队列也可用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留 Stack 类(Deque<Integer>stack=new ArrayDeque<Integer>())。在将双端队列用作堆栈时,元素push到双端队列的开头并从双端队列开头pop。堆栈方法完全等效于 Deque 方法

注意,在将双端队列用作队列或堆栈时,peek 方法同样正常工作,即无论哪种情况下,都从双端队列的开头抽取元素。

Deque 实现通常不定义基于元素的 equals 和 hashCode 方法,而是从 Object 类继承基于身份的 equals 和 hashCode 方法。

Deque方法罗列:

- add(E e) 添加元素至队列的尾部

- addFirst(E e) 将指定元素插入此双端队列的开头

- addLast(E e) 将指定元素插入此双端队列的末尾

- contains(Object o) 如果此双端队列包含指定元素,则返回 true

- descendingIterator() 返回以逆向顺序在此双端队列的元素上进行迭代的迭代器

- element() 获取,但不移除此双端队列所表示的队列的头部

- getFirst() 获取,但不移除此双端队列的第一个元素。

- getLast() 获取,但不移除此双端队列的最后一个元素。

- iterator() 返回以恰当顺序在此双端队列的元素上进行迭代的迭代器。

- offer(E e) 将指定元素插入此双端队列所表示的队列

- peek() 获取,但不移除此双端队列所表示的队列的头部

- poll() 获取并移除此双端队列所表示的队列的头部

- pop() 从此双端队列所表示的堆栈中弹出一个元素

- push(E e) 将一个元素推入此双端队列所表示的堆栈

- remove() 获取并移除此双端队列所表示的队列的头部

- remove(Object o) 从此双端队列中移除第一次出现的指定元素

- removeFirst() 获取并移除此双端队列第一个元素

- removeLast() 获取并移除此双端队列的最后一个元素

- size() 返回此双端队列的元素数

Queue实现类

Queue的实现类有 LinkedList 和 PriorityQueue 。最常用的实现类是 LinkedList 。

LinkedList类是List接口的实现类,但是同时也是 Deque 接口实现类,所以 LinkedList 既可以当做 双端队列 来使用,也可以当做  来使用

LinkedList 是采用 链表 的形式实现的,因此LinkedList 比较适合用于经常插入和删除的操作(链表增删操作为O(1)),而以数组实现的更加适合于随机访问。

232. 用栈实现队列

题目:力扣

使用两个栈来实现队列:

队列是先进先出,而栈是先进后出。

队列和栈不一样的地方主要是出口 - 问题在于如何实现删除队列元素?

那么很巧妙的一个思路就是:

        两个栈,一个栈储存刚刚进入队列的元素 - stack1;另一个栈储存准备出队列的元素 - stack2。

在删除队列元素以及查找队头的方法中:

        先判断准备出队列的栈stack2中是否有元素;若是没有元素,说明需要将入队列的栈stack1中的元素转存过来。

        此处操作为 - 准备出队列的栈stack2一个一个添加入队列的栈stack1中的元素

        会发现此时出队列的栈stack2中元素顺序和队列的顺序是一致的;

        因此,若是stack2中是有元素,那就可以直接把栈顶的元素移除。

代码实现:

class MyQueue {
    LinkedList<Integer> addlist;
    LinkedList<Integer> removelist;

    //两个栈
    //一个栈用来储存存入的元素
    //一个栈用来储存准备删除的元素
    public MyQueue() {
        addlist = new LinkedList<>();
        removelist = new LinkedList<>();
    }
    
    public void push(int x) {
        addlist.push(x);
    }
    
    public int pop() {
        if(removelist.isEmpty()){
            while(!addlist.isEmpty()){
                removelist.push(addlist.poll());
            }
        }

        return removelist.poll();

    }
    
    public int peek() {
         if(removelist.isEmpty()){
            while(!addlist.isEmpty()){
                removelist.push(addlist.poll());
            }
        }

        return removelist.peek();
    }
    
    public boolean empty() {
        return addlist.size() == 0 && removelist.size() == 0;
    }
}

参考资料:代码随想录 

225. 用队列实现栈

 题目:力扣

 同样采用两个队列来实现。但是这里考虑一个作为输出和一个作为输入队列是不可行的,因为把输入队列中的元素放入到输入队列,元素删除的顺序仍然是先入先出。

一种很巧妙的思想是,把其中一个队列作为辅助队列。

在添加元素的时候:

        先将元素放入辅助队列,然后将主队列中的元素一个一个弹出并添加到辅助队列

        此时辅助队列中元素删除的顺序和栈中的顺序是相同的。

class MyStack {
    LinkedList<Integer> main;
    LinkedList<Integer> assistant;

    public MyStack() {
        main = new LinkedList<>();
        assistant = new LinkedList<>();

    }
    
    public void push(int x) {
        assistant.offer(x);
        while(!main.isEmpty()){
            assistant.offer(main.poll());
        }

        LinkedList<Integer> temp = main;
        main = assistant;
        assistant = temp;
    }
    
    public int pop() {
        return main.poll();
    }
    
    public int top() {
        return main.peek();
    }
    
    public boolean empty() {
        return main.size() == 0;
    }
}

参考资料:代码随想录 

20. 有效的括号

 题目:力扣 

括号总共有三种可能,有效的括号是有了左括号就有右括号,且是对称的。有序的

因此可以考虑用栈来储存即将出现的右括号。

        遇到左括号则将对称的右括号放入栈。

        如果栈顶和右括号相等则抛出。

最后判断栈是否为空,如果是空的说明右括号都存在,那么是有效的。

 public boolean isValid(String s) {
        LinkedList<Character> stack = new LinkedList<>();
        for(int i=0; i<s.length(); i++){
            if(!stack.isEmpty() && stack.peek() == s.charAt(i)){
                stack.poll();
            }else{
               
                if(s.charAt(i) == '('){
                    stack.push(')');
                }else if(s.charAt(i) == '['){
                    stack.push(']');
                }else if(s.charAt(i) == '{'){
                    stack.push('}');
                }else{
                    return false;
                }
            
            }

        }

        return stack.isEmpty(); 
    }

参考资料:代码随想录

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

这里仍然可以利用栈结构。

遍历一遍字符串:

        判断元素与栈顶元素是否相同:若相同则删除栈顶元素;若不相同则放入栈。

那么在输出剩下元素的时候需要注意,此时栈中弹出元素顺序刚好和原本字符串中的元素顺序是相反的,因此需要反转。

class Solution {
    public String removeDuplicates(String s) {
        LinkedList<Character> stack = new LinkedList<>();
        for(int i=0; i<s.length(); i++){
            if(!stack.isEmpty() && stack.peek() == s.charAt(i)){
                stack.pop();
            }else{
                stack.push(s.charAt(i));
            }
        }

        String ns = new String();
        while(!stack.isEmpty()){
            ns = stack.poll() + ns;
        }

        return ns;

    }
}

 参考资料:代码随想录

150.逆波兰表达式求值

题目:力扣

仔细研究下后缀表达式,可以发现和上一题消除相邻元素的思路非常像;可以同一个stack来储存将要进行运算的数字;在遇到运算符之后把栈顶处前两个元素取出根据运算符:

                第二次取出的数  (运算符) 第一次取出的数

然后把结果重新放回栈顶。

代码实现:注意 - 和 / 这两个符号中元素的运算顺序

public int evalRPN(String[] tokens) {
        LinkedList<Integer> stack = new LinkedList<>();
        for(int i=0; i<tokens.length; i++){
            if(tokens[i].equals("+")){
                int x = stack.poll();
                int y = stack.poll();
                stack.push(x+y);
            }else if(tokens[i].equals("-")){
                int x = stack.poll();
                int y = stack.poll();
                stack.push(y-x);
            }else if(tokens[i].equals("*")){
                int x = stack.poll();
                int y = stack.poll();
                stack.push(x*y);
            }else if(tokens[i].equals("/")){
                int x = stack.poll();
                int y = stack.poll();
                stack.push(y/x);
            }else{
                stack.push(Integer.parseInt(tokens[i]));
            }
        }

        return stack.poll();
    }

 参考资料:代码随想录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值