代码随想录算法训练营_day10

{{day10}}

栈(Stack)和队列(Queue)都是Java中常用的数据结构,它们在处理数据时遵循不同的原则。

1. 栈 (Stack)

栈是一种遵循后进先出(LIFO, Last-In-First-Out)原则的数据结构。可以将栈想象成一叠盘子,你只能从顶部添加或移除盘子。

Java中的栈主要特点:

  • 插入操作称为"压栈"(push)
  • 删除操作称为"弹栈"(pop)
  • 只能访问栈顶元素
  • Java提供了Stack类,它扩展自Vector类

栈的基本操作:

Stack<Integer> stack = new Stack<>();

// 压栈
stack.push(1);
stack.push(2);
stack.push(3);

// 弹栈
int top = stack.pop(); // 返回3

// 查看栈顶元素但不移除
int peek = stack.peek(); // 返回2

// 检查栈是否为空
boolean isEmpty = stack.empty();

栈的应用场景:

  • 函数调用的实现
  • 表达式求值
  • 深度优先搜索算法
  • 回溯算法

2. 队列 (Queue)

队列遵循先进先出(FIFO, First-In-First-Out)原则。可以将队列想象成排队买票,先到的人先买票离开。

Java中的队列主要特点:

  • 插入操作发生在队尾,称为"入队"(enqueue)
  • 删除操作发生在队首,称为"出队"(dequeue)
  • Java提供了Queue接口,常用实现类有LinkedList和PriorityQueue

队列的基本操作:

Queue<String> queue = new LinkedList<>();

// 入队
queue.offer("A");
queue.offer("B");
queue.offer("C");

// 出队
String first = queue.poll(); // 返回"A"

// 查看队首元素但不移除
String peek = queue.peek(); // 返回"B"

// 检查队列是否为空
boolean isEmpty = queue.isEmpty();

队列的应用场景:

  • 广度优先搜索算法
  • 缓冲区实现
  • 任务调度
  • 消息队列系统

题目信息232. 用栈实现队列

  • 题目链接: https://leetcode.cn/problems/implement-queue-using-stacks/
  • 题目描述:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

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

解题思路

232.用栈实现队列版本2

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

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

代码实现

import org.junit.Test;  
  
import java.util.Stack;  
  

 public class Day10 {  
    @Test  
    public void test1(){  
        MyQueue myQueue = new MyQueue();  
        myQueue.push(1); // queue is: [1]  
        myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)  
        System.out.println(myQueue.peek()); // return 1  
        System.out.println(myQueue.pop()); // return 1, queue is [2]  
        System.out.println(myQueue.empty()); // return false  
    }  
    class MyQueue {  
        Stack<Integer> stackIn;  
        Stack<Integer> stackOut;  
  
        public MyQueue() {  
            stackIn = new Stack<>();  
            stackOut = new Stack<>();  
        }  
  
        public void push(int x) {  
            stackIn.push(x);  
        }  
  
        public int pop() {  
            dumpstackIn();  
            return stackOut.pop();  
        }  
  
        public int peek() {  
            dumpstackIn();  
            return stackOut.peek();  
        }  
  
        public boolean empty() {  
            return stackIn.isEmpty() && stackOut.isEmpty();  
        }  
        public void dumpstackIn(){  
            if (!stackOut.isEmpty()) return;  
            while (!stackIn.isEmpty()){  
                stackOut.push(stackIn.pop());  
            }  
        }  
    }  
}

题目信息 225. 用队列实现栈

  • 题目链接: https://leetcode.cn/problems/implement-stack-using-queues/description/
  • 题目描述: 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppop 和 empty)。

实现 MyStack 类:

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

解法一: {{使用两个 Queue 实现}}

解题思路

![[225.用队列实现栈.gif]]

代码实现

@Test  
public void test2(){  
    MyStack myStack = new MyStack();  
    myStack.push(1);  
    myStack.push(2);  
    System.out.println(myStack.top()); // 返回 2    System.out.println(myStack.pop()); // 返回 2    System.out.println(myStack.empty()); // 返回 False  
}  
class MyStack {  
    Queue<Integer> queue1;  
    Queue<Integer> queue2;  
  
    public MyStack() {  
        queue1 = new LinkedList<>();  
        queue2 = new LinkedList<>();  
    }  
  
    public void push(int x) {  
        queue2.offer(x);  
        while (!queue1.isEmpty()){  
            queue2.offer(queue1.poll());  
        }  
        Queue<Integer> queueTemp;  
        queueTemp = queue1;  
        queue1 = queue2;  
        queue2 = queueTemp;  
    }  
  
    public int pop() {  
        return  queue1.poll();  
    }  
  
    public int top() {  
        return queue1.peek();  
    }  
  
    public boolean empty() {  
        return queue1.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());
    }
}

题目信息 20. 有效的括号

  • 题目链接: https://leetcode.cn/problems/valid-parentheses/
  • 题目描述: 给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

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

注意

  • s.length()
    • 使用场景:当 s 是 String 类型(字符串)时。
    • 解释:length() 是 String 类的一个方法。
    • 示例:

String s = “Hello”;
int length = s.length(); // 返回 5


- 注意:这是一个方法调用,所以需要括号。
- s.length
    - 使用场景:当 `s` 是数组类型时。
    - 解释:`length` 是数组的一个属性(不是方法)。
    - 示例:
```java
int[] s = {1, 2, 3, 4, 5};
int length = s.length; // 返回 5
  • 注意:这是一个属性访问,不需要括号。
  • 区别要点:
    • 方法 vs 属性:length() 是方法,length 是属性。
    • 括号:方法调用需要括号,属性访问不需要。
    • 适用类型:length() 用于 String,length 用于数组。

解题思路

先来分析一下 这里有三种不匹配的情况,

  1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。 括号匹配1

  2. 第二种情况,括号没有多余,但是 括号的类型没有匹配上。 括号匹配2

  3. 第三种情况,字符串里右方向的括号多余了,所以不匹配。 括号匹配3

我们的代码只要覆盖了这三种不匹配的情况,就不会出问题,可以看出 动手之前分析好题目的重要性。

动画如下:

20.有效括号

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

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

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

那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。

分析完之后,代码其实就比较好写了,

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

代码实现

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

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

  • 题目链接: https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/

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

      在 S 上反复执行重复项删除操作,直到无法继续删除。
      
      在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
    

解法一: {{双指针}}

解题思路

  1. 字符串转换:
    • 首先,我们将输入的字符串 s 转换为字符数组 chars。这样做是为了能够直接修改字符,提高效率。
  2. 双指针设置:
    • 我们使用两个指针:slowfast
    • slow 指针:指向当前我们认为有效的(不重复的)字符序列的末尾。
    • fast 指针:用于遍历整个字符数组。
  3. 遍历过程:
    • 我们用 fast 指针从头到尾遍历字符数组。
  4. 字符处理:
    • 对于每个 fast 指向的字符,我们都将其复制到 slow 指针的位置:chars[slow] = chars[fast]
    • 这一步确保了我们不会漏掉任何字符,同时为后续的去重操作做准备。
  5. 去重逻辑:
    • 在复制字符后,我们检查是否需要去除重复:
      • 如果 slow > 0(确保有前一个字符可比较)且当前字符与前一个字符相同(chars[slow] == chars[slow - 1]), 我们就将 slow 指针后退一步(slow--)。这有效地删除了这对重复的字符。
      • 如果不满足上述条件,说明当前字符不需要删除,我们就将 slow 指针前进一步(slow++)。
  6. 结果生成:
    • 遍历结束后,slow 指针的位置恰好是去重后字符串的长度。
    • 我们使用 new String(chars, 0, slow) 创建一个新的字符串,它包含了字符数组从索引0到slow(不包括slow)的所有字符, 这就是我们的最终结果。

代码实现

public String removeDuplicates(String s) {  
    // 将字符串转换为字符数组  
    char[] chars = s.toCharArray();  
    int slow = 0;// 慢指针,指向当前处理的位置  
  
    // 快指针遍历整个字符数组  
    for (int fast = 0; fast < chars.length; fast++){  
        // 将当前字符放到慢指针的位置  
        chars[slow] = chars[fast];  
  
        if (slow > 0 && chars[slow] == chars[slow - 1]){  
            // 如果当前字符与前一个字符相同,慢指针回退  
           slow -- ;  
        }else {  
            // 否则,慢指针前进  
            slow++;  
        }  
    }  
    // 返回处理后的字符串,长度为慢指针的位置  
    return new String(chars,0,slow);  
}

解法二: {{字符串直接作为栈}}

解题思路

我们在删除相邻重复项的时候,其实就是要知道当前遍历的这个元素,我们在前一位是不是遍历过一样数值的元素,那么如何记录前面遍历过的元素呢?

所以就是用栈来存放,那么栈的目的,就是存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。

然后再去做对应的消除操作。 如动画所示:

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

从栈中弹出剩余元素,此时是字符串ac,因为从栈里弹出的元素是倒序的,所以再对字符串进行反转一下,就得到了最终的结果。

代码实现

public String removeDuplicates1(String s) {  
    StringBuffer res = new StringBuffer();  
    int top = -1;  
    for (int i = 0 ; i < s.length();i++){  
        char c = s.charAt(i);  
        if (top >= 0 && res.charAt(top) == c){  
            res.deleteCharAt(top);  
            top--;  
        }else {  
            res.append(c);  
            top++;  
        }  
    }  
    return res.toString();  
}
  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值