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

栈的介绍和实现

栈的介绍

栈(Stack) 是一种后进先出(LIFO, Last In First Out)的数据结构。也就是说,最后一个加入栈的元素是第一个被移除的。栈支持两种主要操作:

  1. push: 将一个元素压入栈顶。
  2. pop: 从栈顶移除并返回元素。

另外,栈还通常支持以下操作:

  • peektop: 返回栈顶元素但不移除它
  • isEmpty: 判断栈是否为空
  • size: 返回栈中元素的数量

Java 中的栈实现

1. java.util.Stack

  • 实现方式: 这是 Java 提供的一个直接的栈类,继承自 Vector 类。它实现了栈的所有基本操作,如 push, pop, peek, isEmpty, 和 search
  • 特点:
    • 由于继承自 Vector,它也是一个动态数组,并且是同步的(线程安全)。
    • 每次 push 操作都会在 Vector 的末尾添加一个元素,pop 操作则会移除末尾的元素。
    • Stack 类的用法相对简单,但由于线程安全的特性,性能在多线程环境下会受到一定的影响。
import java.util.Stack;

public class StackExample {
    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        stack.push(10);
        stack.push(20);
        System.out.println(stack.pop()); // 输出 20
    }
}
实现一个stack
class MyStack {
    private int[] stackArray; // 用于存储栈元素的数组
    private int top;          // 栈顶指针,指向下一个可用位置
    private int capacity;     // 栈的容量

    // 构造函数,初始化栈的容量
    public MyStack(int capacity) {
        this.capacity = capacity;
        stackArray = new int[capacity];
        top = 0; // 初始时栈顶指针为0,表示栈为空
    }

    // 压栈操作,将元素压入栈中
    public void push(int value) {
        if (isFull()) {
            System.out.println("栈已满,无法再压入元素");
            return;
        }
        stackArray[top++] = value; // 先赋值,再将top加1
    }

    // 出栈操作,将栈顶元素弹出栈
    public int pop() {
        if (isEmpty()) {
            System.out.println("栈为空,无法弹出元素");
            return -1; // 通常返回一个特定值表示栈为空
        }
        return stackArray[--top]; // 先将top减1,再取值
    }

    // 查看栈顶元素
    public int peek() {
        if (isEmpty()) {
            System.out.println("栈为空,没有栈顶元素");
            return -1;
        }
        return stackArray[top - 1]; // 取top指针前一位置的值
    }

    // 判断栈是否为空
    public boolean isEmpty() {
        return top == 0;
    }

    // 判断栈是否已满
    public boolean isFull() {
        return top == capacity;
    }

    // 返回栈中元素的个数
    public int size() {
        return top; // top指针指向下一个可用位置,top即为元素个数
    }
}

2. java.util.Deque (双端队列)

  • 实现方式: Deque 接口提供了比 Stack 更通用和灵活的栈操作。常见的实现类包括 ArrayDequeLinkedList
  • 特点:
    • Deque 提供了 push, pop, 和 peek 方法,这些方法可以将 Deque 用作栈。
    • ArrayDeque 是基于动态数组的实现,性能比 Stack 更好,且不需要线程同步。
    • LinkedList 是基于双向链表的实现,适合于需要频繁插入和删除的场景。
import java.util.ArrayDeque;
import java.util.Deque;

public class DequeAsStackExample {
    public static void main(String[] args) {
        Deque<Integer> stack = new ArrayDeque<>();
        stack.push(10);
        stack.push(20);
        System.out.println(stack.pop()); // 输出 20
    }
}

3. java.util.LinkedList

  • 实现方式: LinkedList 类既可以作为栈,也可以作为队列使用,因为它实现了 Deque 接口。
  • 特点:
    • LinkedList 的每个元素都是一个节点,包含指向前一个和后一个节点的引用,因此 pushpop 操作的时间复杂度为 O(1)。
    • LinkedList 在频繁的插入和删除操作中表现良好,但相比 ArrayDeque,访问元素的速度较慢。
import java.util.LinkedList;

public class LinkedListAsStackExample {
    public static void main(String[] args) {
        LinkedList<Integer> stack = new LinkedList<>();
        stack.push(10);
        stack.push(20);
        System.out.println(stack.pop()); // 输出 20
    }
}

4. java.util.ArrayDeque

  • 实现方式: ArrayDeque 是基于数组的双端队列,提供栈和队列的双重功能。
  • 特点:
    • 动态数组结构,不会像 Stack 那样受到线程同步的性能影响。
    • LinkedList 不同,ArrayDeque 不会消耗额外的空间来存储链表节点指针。
    • 适合于栈操作和队列操作混用的场景。
import java.util.ArrayDeque;

public class ArrayDequeAsStackExample {
    public static void main(String[] args) {
        ArrayDeque<Integer> stack = new ArrayDeque<>();
        stack.push(10);
        stack.push(20);
        System.out.println(stack.pop()); // 输出 20
    }
}

队列的介绍和实现

队列的介绍

队列(Queue) 是一种先进先出(FIFO, First In First Out)的数据结构。这意味着在队列中,最早加入的元素会最先被移除。队列常用于需要按顺序处理任务或数据的场景,例如在操作系统中管理进程或在网络服务器中处理请求。

队列的基本操作:

  1. 入队(Enqueue):将一个元素添加到队列的尾部。
  2. 出队(Dequeue):移除并返回队列的头部元素。
  3. 查看队头元素(Peek or Front):查看但不移除队列的头部元素。
  4. 判断队列是否为空(IsEmpty)
  5. 判断队列是否已满(IsFull)(在固定大小的队列中)。

队列的实现

可以用数组或链表来实现队列。下面是一个用数组实现的简单队列示例。

双向链表实现

public class Queue {
    // 双向链表节点
    public static class ListNode {
        ListNode next;
        ListNode prev;
        int value;

        // 构造函数,初始化节点
        ListNode(int value) {
            this.value = value;
        }
    }

    // 队头和队尾指针
    private ListNode first; // 队头
    private ListNode last;  // 队尾
    private int size = 0;   // 队列大小

    // 入队列操作---向双向链表的尾部插入新节点
    public void offer(int e) {
        ListNode newNode = new ListNode(e);
        if (first == null) {    // 如果队列为空,新节点既是队头也是队尾
            first = newNode;
        } else {                // 如果队列非空,将新节点插入到队尾
            last.next = newNode;
            newNode.prev = last;
        }
        last = newNode; // 更新队尾指针
        size++;
    }

    // 出队列操作---将双向链表的第一个节点删除
    public int poll() {
        if (first == null) {    // 队列为空,返回 -1 表示无元素
            return -1;
        }
        
        int value = first.value;

        if (first == last) {    // 队列中只有一个元素,删除后队列变空
            last = null;
            first = null;
        } else {                // 队列中有多个元素,删除第一个节点
            first = first.next;

            first.prev = null;
        }

        size--; // 减少队列大小
        return value;
    }

    // 获取队头元素---返回链表中第一个节点的值
    public int peek() {
        if (first == null) {    // 队列为空,返回 -1 表示无元素
            return -1;
        }
        return first.value;
    }

    // 获取队列的大小
    public int size() {
        return size;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return first == null;
    }
}

java自带

Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.offer(2);
System.out.println(queue.poll()); // 输出 1
System.out.println(queue.peek()); // 输出 2

 232.用栈实现队列

题目链接:用栈实现队列

1.解题思路

用两个栈表示队列,一个是入栈,一个是存放出栈,此时就可以实现队列的进出了

out出栈,一定到出完,再把in的全部pop出,push进out

2.我的实现

实现遇到的问题

代码

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


    public MyQueue() {
        this.stackIn=new Stack<>();
        this.stackOut=new Stack<>();

    }
    
    public void push(int x) {
        stackIn.push(x);

    }
    
    public int pop() {

        if (!stackOut.isEmpty()){
            return stackOut.pop();
        }
        while (!stackIn.isEmpty()){
            stackOut.push(stackIn.pop());
        }
        return stackOut.pop();
    }
    
    public int peek() {

        if (!stackOut.isEmpty()){
            return stackOut.peek();
        }
        while (!stackIn.isEmpty()){
            stackOut.push(stackIn.pop());
        }
        return stackOut.peek();
        
    }
    
    public boolean empty() {
        return stackIn.isEmpty()&&stackOut.isEmpty();

    }
}

3.标准实现

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());
        }
    }
}

4.题目总结

解题思路里

 225. 用队列实现栈

题目链接:用队列实现栈

1.解题思路

两个队列实现栈:

每次push进入栈时,当第一个元素push时,都让q1的元素到q2,此时q1为空,然后再add这个元素,然后就可以把q2中的元素add到q1,此时q1就是一个倒序的队列了,可以直接实现栈了。

一个队列实现:

把元素0取出来再加入到队列,把元素1取出来再加入到队列,直到最后一个元素

即把size-1个元素弹出来,取最后一个元素

2.我的实现

实现遇到的问题

代码

两个队列实现栈

class MyStack {//用两个队列实现

    Queue<Integer> q1=new ArrayDeque<>();
    Queue<Integer> q2=new ArrayDeque<>();


    public MyStack() {

    }
    
    public void push(int x) {

        while (!q1.isEmpty()){
            q2.add(q1.poll());
        }
        q1.add(x);
        while (!q2.isEmpty()){
            q1.add(q2.poll());
        }
        
    }
    
    public int pop() {

       return q1.poll();
    }
    
    public int top() {

        return q1.peek();
    }
    
    public boolean empty() {

        return q1.isEmpty();
    }
}

一个队列实现:

public class MyStack2 {
    Queue<Integer> q1=new ArrayDeque<>();

    int size=0;
    int tmp=0;

    public MyStack2() {

    }

    public void push(int x) {

        q1.add(x);
        size++;

    }

    public int pop() {

        size--;
        tmp=size;
        while (tmp>0){
            q1.add(q1.poll());
            tmp--;

        }
        return q1.poll();
    }

    public int top() {

        tmp=size-1;
        while (tmp>0){
            q1.add(q1.poll());
            tmp--;

        }
        int res= q1.peek();
        q1.add(q1.poll());
        return res;
    }

    public boolean empty() {

        return q1.isEmpty();
    }
}

3.标准实现

两个队列

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

    public MyStack() {

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

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

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

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

一个队列

class MyStack {
    Queue<Integer> queue;
    
    public MyStack() {
        queue = new LinkedList<>();
    }
    
    public void push(int x) {
        queue.add(x);
    }
    
    public int pop() {
        rePosition();
        return queue.poll();
    }
    
    public int top() {
        rePosition();
        int result = queue.poll();
        queue.add(result);
        return result;
    }
    
    public boolean empty() {
        return queue.isEmpty();
    }

    public void rePosition(){
        int size = queue.size();
        size--;
        while(size-->0)
            queue.add(queue.poll());
    }
}

4.题目总结

解题思路里

20. 有效的括号

题目链接:有效的括号

1.解题思路

匹配不上就3种情况:

1.左括号多

2.右括号多

3.左右括号不匹配

遇到左括号,就把对应的右括号放到栈里,遇到右括号就与栈顶的右括号匹配。然后此时用右括号如果等于栈的就消除,不等时就不匹配,消完时,右括号还有说明右括号多,栈里还有说明左括号多

2.我的实现

实现遇到的问题

判断stack是否为空

stack == null 检查错误:

  • stack 不会为 null,因为它已经被初始化为一个空的 Stack 对象。
  • 你应该检查的是 stack.isEmpty(),而不是 stack == null

代码

public boolean isValid(String s) {
        Stack<Character> stack=new Stack<>();

        if (s.length()%2!=0){
            return false;
        }
        for (char x:
             s.toCharArray()) {
            //以下3个是用来收集左括号的
            if (x=='('){
                stack.push(')');
                continue;
            }
            if (x=='['){
                stack.push(']');
                continue;
            }
            if (x=='{'){
                stack.push('}');
                continue;
            }

            //如果是右括号
            //先要判断是不是空栈,空栈操作会报错
            //左括号少了
            if (stack.isEmpty()){
                return false;
            }
            //左右括号不匹配
            if (x!=stack.peek()) {
                return false;
            }
            //匹配左右括号
            if (x==stack.peek()){
                stack.pop();
            }
        }
        //右括号少了
        return stack.isEmpty();

    }

3.标准代码

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();
    }
}

4.题目总结

解题思路里

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

题目链接:删除字符串中的所有相邻重复项

1.解题思路

遍历字符串,每遍历一个就储存到栈了,判断是否相等

可以·用一个字符串类型去实现栈中的这个功能。

2.我的实现

实现遇到的问题

代码

public String removeDuplicates(String s) {

        StringBuilder result =new StringBuilder("");
        for (int i = 0; i < s.length(); i++) {
            if (result.length()==0||s.charAt(i)!=result.charAt(result.length()-1)){
                result.append(s.charAt(i));
            }else {
                //删除最后一个相等处的元素
                result.deleteCharAt(result.length()-1);
            }

        }
        return result.toString();
    }

3.标准代码

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;
    }
}

4.题目总结

解题思路里

  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值