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

今日内容

  • 栈与队列理论基础
  • leetcode. 232 用栈实现队列
  • leetcode. 225 用队列实现栈
  • leetcode. 20 有效的括号
  • leetcode. 1047 删除字符串中的所有相邻重复项

栈与队列理论基础

栈和队列都是数据结构,其中栈的特点是先入后出,队列是先入先出。

但是我们往往都是直接使用它们,却很少对它们的底层原理进行研究。

在Java中,栈 可以使用Stack来实现。但是官方实际上并不太推荐使用Stack,而是推荐使用Deque(双向队列)来实现栈的功能。Deque里是包含了栈的操作的。

理由就在于,Stack继承自Vector,它的底层由数组实现,并且它也是JDK 1.0 时代的产物了。Vector在方法上添加了synchronized来达到线程安全的目的,但是它特别耗资源,有时候可能栈的实现并不要求线程安全。

而队列继承自Collection接口,比较常见的是用 ArrayQueue 或 LinkedList 来实现,即分别由数组和链表实现。

Leetcode. 232 用栈实现队列

文章链接:代码随想录 (programmercarl.com)

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

该题用于熟悉栈和队列的操作,对算法并没有什么要求。

栈和队列的区别就在于,前者先入后出,后者先入先出。要想让栈实现队列,就需要先把输出次序对齐。

所以我们可以做两个栈,入队操作就按照入栈操作执行,将元素推入stackIn栈。出队操作则先将stackIn的元素推出到stackOut的栈,最后再将stackOut栈的元素推出。在两个栈之间进行元素转移时,元素的输出次序也就对齐了。

实现代码如下:

class MyQueue {
    Deque<Integer> stackIn; // 模拟队列输入
    Deque<Integer> stackOut; // 模拟队列输出

    // 初始化
    public MyQueue() {
        stackIn = new LinkedList<>();
        stackOut = new LinkedList<>();
    }
    
    public void push(int x) {
        while (!stackOut.isEmpty()){ // 先将输出栈的元素全部移回来,保证原先次序
            stackIn.push(stackOut.pop());
        }
        stackIn.push(x);
    }
    
    public int pop() {
        while (!stackIn.isEmpty()){ // 将输入栈的元素全部移到输出栈,保证符合队列输出次序
            stackOut.push(stackIn.pop());
        }
        int out = stackOut.pop();
        return out;
    }
    
    public int peek() {
        while (!stackIn.isEmpty()){
            stackOut.push(stackIn.pop());
        }
        int out = stackOut.peek();
        return out;
    }
    
    public boolean empty() {
        if (stackIn.isEmpty() && stackOut.isEmpty()){ // 若输入栈和输出栈全空则说明空了
            return true;
        } else {
            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();
 * boolean param_4 = obj.empty();
 */
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

Leetcode. 225 用队列实现栈

文章链接:代码随想录 (programmercarl.com)

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

和上一题差不多思路,不过队列比较特殊,既可以用两个队列实现,也可以直接用一个队列实现。

两个队列的思路和上题差不多,注意好输出次序就好,代码如下:

class MyStack {

    Queue<Integer> data;
    Queue<Integer> backup;

    // 初始化
    public MyStack() {
        data = new LinkedList<>();
        backup = new LinkedList<>();
    }
    
    public void push(int x) {
        data.offer(x);
    }
    
    public int pop() {
        int index = top();
        transToBackup(data, backup);  
        data.poll();
        transToData(data, backup);
        return index; 
    }
    
    public int top() {
        transToBackup(data, backup);
        int index = data.peek();
        transToData(data, backup);
        return index;
    }
    
    public boolean empty() {
        if (data.size() == 0 && backup.size() == 0){return true;}
        return false;
    }

    // 将data队列中除最后一个元素都移动到backup队列中
    public void transToBackup(Queue<Integer> data, Queue<Integer> backup){
        while (data.size() != 1){
            backup.offer(data.peek());
            data.poll();
        }
    }

    // 将backup队列中所有元素都移回data队列中
    public void transToData(Queue<Integer> data, Queue<Integer> backup){
        // 如果data队列不为空就挪到backup队列中
        if (!data.isEmpty()){
            backup.offer(data.peek());
            data.poll();
        }
        // 此时backup队列中的元素就是按照原先顺序排列
        // 再挪回data队列中
        while (backup.size() != 0){
            data.offer(backup.peek());
            backup.poll();
        }
    }
}

/**
 * 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();
 * boolean param_4 = obj.empty();
 */
  • 时间复杂度:pop 和 top 操作为O(n),其余操作为O(1)
  • 空间复杂度:O(n)

还可以直接使用一个双向队列来实现,代码如下:

class MyStack {

    Deque<Integer> queue;

    // 初始化
    public MyStack() {
        queue = new LinkedList<>();
    }
    
    public void push(int x) {
        queue.offerLast(x);
    }
    
    public int pop() {
        int out = queue.peekLast();
        queue.pollLast();
        return out;
    }
    
    public int top() {
        int out = queue.peekLast();
        return out;
    }
    
    public boolean empty() {
        if (queue.isEmpty()){
            return true;
        } else {
            return false;
        }
    }

}

/**
 * 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();
 * boolean param_4 = obj.empty();
 */
  • 时间复杂度:O(1)
  • 空间复杂度:O(n)

Leetcode. 20 有效的括号

文章链接:代码随想录 (programmercarl.com)

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

括号匹配是经典的用栈解决的问题。 

在面对这种问题时,需要事先将可能出现的情况列出来,否则会越做越糊涂。深有体会啊,写着写着就被莫名其妙绕昏了。

不匹配的情况有如下几种:

  1. 字符串遍历完,但栈还有元素。即字符串中的左括号多余了。

  2. 还没遍历完,栈就已经空了。即字符串右括号有多余。

  3. 括号没有多余,但是类型没有匹配上。

根据上述列出的情况,我们写出如下代码:

class Solution {
    public boolean isValid(String s) {
        if (s.length() % 2 != 0){return false;} // 字符串长度为奇数的话,肯定配不了对
        Deque<Character> stack = new LinkedList<>();
        char[] array = s.toCharArray();

        for (int i = 0; i < array.length; i++){
            char ch = array[i];
            if (ch == '('){
                stack.push(')');
            } else if (ch == '{'){
                stack.push('}');
            } else if (ch == '['){
                stack.push(']');
            } else if (stack.isEmpty() || stack.peek() != ch){ // 判断情况 2 和 3
                return false;
            } else {
                stack.pop();
            }
        }
        return stack.isEmpty(); // 判断情况 1
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n) 

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

文章链接:代码随想录 (programmercarl.com)

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

其实这也可以视作一个匹配问题。思路就是先将字符串元素推入栈中,然后比较栈顶元素和当前字符,如果相同,则说明是相邻重复项,推出栈;如果不同则不是,入栈。

根据该思路,写出如下代码:

class Solution {
    public String removeDuplicates(String s) {
        Deque<Character> stack = new LinkedList<>();
        for (int i = 0; i < s.length(); i++){
            char ch = s.charAt(i);
            if (i == 0){ // 第一个元素直接入栈
                stack.push(ch);
                continue;
            }
            if (stack.isEmpty() || stack.peek() != ch){ // 当栈空了或者不是相邻重复项时入栈
                stack.push(ch);
            } else {
                stack.pop(); // 当栈非空且是相邻重复项时出栈
            }
        }
        StringBuilder sb = new StringBuilder();
        while (!stack.isEmpty()){
            sb.append(stack.pop());
        }
        return sb.reverse().toString(); // 将栈中元素返回
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n) 

总结

开始栈和队列,栈和队列一直以比较隐蔽的方式出现在我们的生活中,所以还是比较重要的数据结构。

通过本节对栈和队列的Java底层有所了解了,并且对它们的操作也更加熟悉了。最近马上要开学了,估计要忙起来了,继续坚持! 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DonciSacer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值