代码随想录--栈与队列章节总结

news2024/11/13 9:27:30

代码随想录–栈与队列章节总结

1.LeetCode232 用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:

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

你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

解题思路:栈的操作是先进后出的,队的先进先出。通过两个栈,正好可以把入栈顺序反转,实现队列的先进先出。利用两个栈即可实现队列操作

  • 入队:将元素直接入栈到s1
  • 出队:因为出队的顺序和出栈的顺序正好相反,因此需要另一个栈s2把顺序反转一下。出栈时如果s2中没有元素,则需要将s1中所有元素入栈到s2中。然后在从s2出栈一个元素
  • peek:和出队操作一样,只不过这一次时直接返回s2栈顶元素的值,而不进行出栈
  • 判断队列是否为空:只要s1和s2有一个不为空,则队列都不为空。
class MyQueue {
    private Stack<Integer> stack1;
    private Stack<Integer> stack2;

    public MyQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }

    public void push(int x) {
        // 将元素保存到s1
        stack1.push(x);
    }

    public int pop() {
        // 判断s2是否为空,如果为空,则将s1中元素依次入栈到s2中
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) stack2.push(stack1.pop());
        }
        // s2出栈
        return stack2.pop();
    }

    public int peek() {
        // 判断s2是否为空,如果为空,则将s1中元素依次入栈到s2中
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) stack2.push(stack1.pop());
        }
        // s2出栈
        return stack2.peek();
    }

    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }
}

2.LeetCode225 使用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

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

注意:

你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

解题思路1: 使用两个队列实现。

  • 入栈:直接将元素入队列到q1
  • 出栈:把q1中的size-1个元素都保存到q2中,然后q1出队队后一个元素
  • 栈顶:q1中最后一个元素就是栈顶元素
  • 判空:两个队列都为空的时候,则栈为空
class MyStack {
    Deque<Integer> queue1;
    Deque<Integer> queue2;
    public MyStack() {
        queue1 = new ArrayDeque<>();
        queue2 = new ArrayDeque<>();
    }

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

    public int pop() {
        // 将队列1中的size-1个元素出队,存入队列2中
        // 这个地方这个size一定要在循环外面获取
        int size = queue1.size();
        // queue1.size() - 1 这样写不对,因为循环一次queue1.size()就会变化
        // for (int i = 0; i < queue1.size() - 1; i++) {
        for (int i = 0; i < size - 1; i++) {
            queue2.offer(queue1.poll());
        }
        Deque<Integer> temp = queue1;
        queue1 = queue2;
        queue2 = temp;
        return queue2.poll();
    }

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

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

这个地方使用了Deque而不是Queue,主要原因是因为Queue无法使用peekLast()函数,无法获取一个队列的队尾元素。

图解:

image-20230124144917966

解题思路2:同样利用双队列

  • 入栈:首先将元素入队到q2,然后判断q1中是否有元素,如果有元素,将q1中元素都入队到q2。然后交换q1和q2
  • 出队:直接从q1出队
  • 栈顶:q1队首就是栈顶

这个的思路是始终让q1中元素顺序和出栈顺序保持一致

class MyStack {
    Queue<Integer> queue1;
    Queue<Integer> queue2;
    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }

    public void push(int x) {
        // 始终让q1中元素顺序和栈中顺序一致
        // 插入元素,先放到q2中
        queue2.offer(x);
        // 判断q1中是否有元素,如果有,则把q1中的元素都移动的q2中
        while (!queue1.isEmpty()) queue2.offer(queue1.poll());
        // 交换q1和q2
        Queue<Integer> temp = queue1;
        queue1 = queue2;
        queue2 = temp;
    }

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

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

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

图解:

image-20230124145925348

解题思路3:使用一个队列。

  • 入栈:正常入队
  • 出栈:将队列中前size-1个元素都出队,然后依次插入到队尾中。结束以后,队首元素就是要出栈的元素
  • peek:队中最后一个元素
class MyStack {

    Deque<Integer> queue;

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

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

    public int pop() {
        int size = queue.size();
        size--;
        // 将 que1 导入 que2 ,但留下最后一个值
        while (size-- > 0) {
            queue.addLast(queue.peekFirst());
            queue.pollFirst();
        }

        int res = queue.pollFirst();
        return res;
    }

    public int top() {
        return queue.peekLast();
    }

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

image-20230124151524254

3.Leetcode20 有效的括号

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

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

解题思路:使用栈

  • 如果字符串长度小于2,那么直接返回false
  • 只要碰到( [ { 就入栈,碰到其他的就匹配出栈
  • 在else中需要先判断一下栈是否为空,如果为空,则直接返回false
  • 遍历完整个字符串后,需要判断栈是否为空,如果不为空,则匹配失败。
public boolean isValid(String s) {
    // 如果字符串长度为1,则直接返回false
    if (s.length() <= 1) return false;
    // 创建一个栈
    Stack<Character> stack = new Stack<>();
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (c == '(' || c == '{' || c == '[') {
            // 入栈
            stack.push(c);
        } else {
            // 如果栈为空,也就是截止到目前都匹配上了,这时候如果出现)},则一定匹配出错
            // 举个例子输入字符串是")}"
            if (stack.isEmpty()) return false;
            // 将取出的元素c和栈顶元素进行比较,如果相等,则栈顶元素出栈
            // 如果不相等,则返回false
            if (c == ')' && stack.peek() == '(')
                stack.pop();
            else if (c == ']' && stack.peek() == '[')
              	stack.pop();
            else if (c == '}' && stack.peek() == '{')
              	stack.pop();
            else
              	return false;
        }
    }
    // 最后需要判断栈是否为空,如果不为空,则匹配失败
    // 举个例子"(("
    return stack.isEmpty();
}

图解:

image-20230124154051942

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

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

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例:
输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

解题思路: 这个题可以使用栈来解决。

  • 首先判断字符串长度,如果小于2,则直接返回
  • 创建一个栈,让字符串的第一元素入栈。
  • 然后遍历字符串中的每一个元素,和栈顶元素做比较,如果相等,则栈顶元素出栈。如果栈为空或者栈顶元素不相等,则让当前遍历的元素入栈
  • 直到整个字符串被遍历完毕后。使用StringBuilder构建字符串即可。
public static String removeDuplicates(String s) {
    // 如果字符串长度小于2,直接返回即可
    if (s.length() < 2) return s;
    // 创建一个栈
    Stack<Character> stack = new Stack<>();
    // 第一个元素直接入栈
    stack.push(s.charAt(0));
    for (int i = 1; i < s.length(); i++) {
        // 如果栈顶元素和取出来的元素不相等,或者栈为空,则将元素入栈
        if (stack.isEmpty() || stack.peek() != s.charAt(i))
            stack.push(s.charAt(i));
        else
            // 如果相等,则出栈
            stack.pop();
    }
    // 栈中所有元素出栈
    StringBuilder stringBuilder = new StringBuilder();
    while (!stack.isEmpty()) {
      	stringBuilder.append(stack.pop());
    }
    return stringBuilder.reverse().toString();
}

图解:

image-20230124160823955

5.Leetcode105 逆波兰式的求解

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

有效的算符为 ‘+’、‘-’、‘*’ 和 ‘/’ 。
每个操作数(运算对象)都可以是一个整数或者另一个表达式。
两个整数之间的除法总是 向零截断 。
表达式中不含除零运算。
输入是一个根据逆波兰表示法表示的算术表达式。
答案及所有中间计算结果可以用 32 位 整数表示。

示例 1:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

解题思路: 利用栈来解决。

  • 首先判断给出的数组的长度是不是小于等于2,如果是,则报错。因为操作至少需要两个操作数和一个操作符才行。
  • 将数组中的前两个元素入栈。前两个元素一定是数字。
  • 开始遍历字符串数组,每遍历一个就判断一下是否是运算符,如果是,则将栈中出栈两个元素,并按照运算符进行运算,并且将结果入栈
  • 如果不是运算符,则直接将元素入栈
  • 遍历完成后,栈中的唯一一个元素就是逆波兰式的结果。
public int evalRPN(String[] tokens) {
    if (tokens.length <= 2) return Integer.parseInt(tokens[0]);

    // 创建一个栈
    Stack<Integer> stack = new Stack<>();
    // 前两个元素入栈
    stack.push(Integer.parseInt(tokens[0]));
    stack.push(Integer.parseInt(tokens[1]));

    // 开始遍历
    for (int i = 2; i < tokens.length; i++) {
        String token = tokens[i];
        // 判断token是运算符么
        if (isOperation(token)) {
          // 先出栈两个操作数
            Integer b = stack.pop();
            Integer a = stack.pop();
            Integer res = null;
            switch (token) {
                case "+" :
                    res = a + b;
                    break;
                case "-" :
                    res = a - b;
                    break;
                case "*":
                    res = a * b;
                    break;
                case "/" :
                    res = a / b;
                    break;
            }
            // 将结果入栈
            stack.push(res);
        }
        else {
            // 不是运算符那么直接入栈
            stack.push(Integer.parseInt(token));
        }
    }
    return stack.pop();
  }
private boolean isOperation(String token) {
    if (token.equals("*") || token.equals("+") || token.equals("-") || token.equals("/")) 
        return true;
    return false;
}

这里需要注意的一点是如果表示式为[“6”,“3”,“/”],他对应的计算应该是6/3还是3/6

在这个题中,给出了栗子,应该是6/3

image-20230124171516024

6.求TopK的数

给定一个很大的数组,长度100w,求第k大的数是多少?

这个问题是一个很经典的问题,如果采用传统方式,即现排序,然后找到第k个数,对于数据量很大的时候,是非常耗时的。对于这个问题,最好的解决方案是使用堆排序。

  • 具体来说,首先取数组中前k个字符,保存到堆中,顺序堆会自动调整。
  • 然后从k+1开始遍历数组,每次都和堆顶元素进行比较。如果我们要求第k大的数,那么需要建立小顶堆。那么遍历的元素如果比堆顶元素小,那么堆顶弹出,把遍历元素放入堆中。
  • 完全遍历之后,堆顶元素就是第k大的元素。

求第k个大的数,需要建立小顶堆。小顶堆的意思是堆顶元素最小,那么在这个有k个元素的堆中,k-1个元素都比堆顶元素大,那么堆顶元素不就是第k大的了么。

同理如果求第k小的数,需要建立大顶堆。在堆中,k-1个元素都比堆顶元素小,那么堆顶元素就是第k小的元素。

在Java中,我们使用优先队列来实现上述操作,因为优先队列的底层实现就是堆。

// 求第k小的数
private static int heap(int[] arr, int k) {
    // 创建一个大小为k的,大顶堆
    PriorityQueue<Integer> queue = new PriorityQueue<>(k, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    });
    // 循环整个数组,让前k个元素直接存入大顶堆中,从第k+1个元素开始需要和堆顶进行比较
    for (int i = 0; i < arr.length; i++) {
        if (queue.size() < k) {
            queue.offer(arr[i]);
        } else {
            int top = queue.peek();
            if (top > arr[i]) {
                queue.poll();
                queue.offer(arr[i]);
            }
        }
    }
    return queue.poll();
}

这里我们需要注意的是,优先队列默认的是小顶堆,如果我们要创建大顶堆,需要重写比较器。

下面进行了对比实验,对比了直接排序法和使用堆排序的耗时

public static void main(String[] args) {
      int k = 25;
      int[] arr = new int[200000000];
      Random random = new Random();
      for (int i = arr.length - 1, j = 0; i >= 0; i--) {
          arr[i] = random.nextInt();
      }
      Long start = System.currentTimeMillis();
      int sort = sort(arr, k); // 直接排序
      Long end = System.currentTimeMillis();
      System.out.print("使用普通排序耗时:");
      System.out.println(end - start);
      System.out.println(sort);

      Long start1 = System.currentTimeMillis();
      int heap = heap(arr, k);
      Long end1 = System.currentTimeMillis();
      System.out.print("使用heap排序耗时:");
      System.out.println(end1 - start1);
      System.out.println(heap);
  }

private static int sort(int[] arr, int k) {
    Arrays.sort(arr);
    return arr[k - 1];
}
使用普通排序耗时:24185ms
第k小的数为:-2147482892
使用heap排序耗时:528ms
第k小的数为:-2147482892

从结果可以看出来差距还是和明显的

这里需要注意判断是否需要出堆顶元素的条件

  • 对于大顶堆来说 top > arr[i]
  • 对于小顶堆来说 top < arr[i]

下面举一个例子,求第三小的数,图解如下:

image-20230124212805629

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/178010.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2.4.2 浮点型

1.浮点型基本数据类型介绍 浮点类型用于表示有小数部分的数值。在JAVA种有俩种浮点类型&#xff0c;分别是float和double. 类型字节长度位数取值范围float4字节32约 3.40282347E38Fdouble8字节64约 1.79769313486231570E308 double表示这种类型的数值精度是float类型的俩倍&a…

PyCharm中运行LeetCode中代码

Leetcode中题目只需要写函数体里面内容即可。不需要关注输入&#xff0c;输出。 这里拿LeetCode中第一题&#xff0c;两数之和 “给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数…

随机梯度下降算法 入门介绍(最通俗易懂)

文章目录1.什么是梯度2.什么是梯度下降算法3.什么是随机梯度下降算法1.什么是梯度 首先给出高数课程中梯度的定义&#xff1a; 如果对上面的定义没有理解也没有关系&#xff0c;用通俗的语言来说&#xff0c;梯度实际上就是一个向量&#xff0c;向量中的各个元素表示多元函数在…

Java IO流之字节流详解

一、OutputStream输出流 FileOutputStream概述&#xff1a; 操作本地文件的字节输出流&#xff0c;可以把程序中的数据写到本地文件中 书写步骤&#xff1a; ① 创建字节输出流对象 细节1&#xff1a;参数一是字符串表示的路径或者File对象都是可以的 细节2&#xff1a;如果文…

《安富莱嵌入式周报》第300期:几百种炫酷灯阵玩法, USB Web网页固件升级,波士顿动力整活,并联二极管问题,VisualStudio升级,STM32C0

往期周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 祝大家春节快乐&#xff01; 视频版&#xff1a; https://www.bilibili.com/video/BV1UY4y1d7C7 《安富莱嵌入式周…

(考研湖科大教书匠计算机网络)第二章物理层-第三、四节:传输方式和编码与调制

文章目录一&#xff1a;传输方式&#xff08;1&#xff09;串行传输和并行传输&#xff08;2&#xff09;同步传输和异步传输&#xff08;3&#xff09;单工、半双工和全双工二&#xff1a;编码与调制&#xff08;1&#xff09;通信相关基础知识①&#xff1a;消息、数据、信号…

linux实战笔记整理(1.24)

后台挂起程序: 如果报错&#xff1a;nohup: ignoring input and appending output to nohup.out&#xff0c;则在指令最后加一个&让程序自己运行&#xff1a;nohup command增加rm 提醒&#xff1a;&#xff08;重要的命令问三遍&#xff09;&#xff1a; 操作&#xff1a; …

为tableview添加带控件的单元格如复选框checkbox与combbox单元格

我们常常会有这样的需求&#xff0c;为QTableView增加复选框checkbox和选择下拉框combbox&#xff0c;毕竟依靠键盘输入不是很好约束其规范性。下面我们逐个来介绍。完成之后的效果如下&#xff1a; 一、准备TableView 1、数据准备 数据大家可以随意准备&#xff0c;有的话…

[C语言]柔性数组

目录 1.柔性数组 2.柔性数组的特点 3.柔性数组的使用 1.柔性数组 柔性数组存在于结构体中&#xff0c;当结构体最后一个成员元素为一个未知大小的数组&#xff0c;则称其为柔性数组。 struct s {int a;char arr[];//柔性数组 }; 2.柔性数组的特点 1. 结构中的柔性数…

八、python-高级语法篇(黑马程序猿-python学习记录)

黑马程序猿的python学习视频&#xff1a;https://www.bilibili.com/video/BV1qW4y1a7fU/ 目录 1. 什么是闭包 2. 闭包的优缺点 3. nonlocal关键字的作用 4. 简单闭包 5. 使用nonlocal关键字修改外部函数的值 6. 使用闭包实现ATM小案例 7. 装饰器概念 8. 装饰器的一般写法 9. 装…

Vue-Router详解

1、前端路由的发展历程 1.1、认识前端路由 路由其实是网络工程中的一个术语&#xff1a; 在架构一个网络时&#xff0c;非常重要的两个设备就是路由器和交换机。当然&#xff0c;目前在我们生活中路由器也是越来越被大家所熟知&#xff0c;因为我们生活中都会用到路由器&…

算法:树状数组详解(c++实现 求解动态数组区间和问题)

文章目录引入树状数组c完整代码引入 什么是树状数组&#xff1f;&#xff1f;&#xff1f; 解决数据压缩里的累积频率&#xff08;Cumulative Frequency&#xff09;的计算问题&#xff0c;现多用于高效计算数列的前缀和&#xff0c; 区间和&#xff0c;这样的问题可以使用树…

Day8 spring 注解开发

1 前言使用注解类代替xml配置&#xff0c;使用注解代理xml中的标签2 标签2.1 用于Bean(类)上的常见注解2.1.1 Component步骤&#xff1a;在xml中配置包扫描&#xff0c;使得spring知道在哪些类上加上Component在特定类上加上Component测试/*** author : msf* date : 2023/1/24*…

#I. 哆啦A梦的时光机(bfs经典习题)

题目说明有一天&#xff0c;大雄和他的伙伴们想穿越时空进行探险&#xff0c;可是时光机却出了一点故障&#xff0c;只能进行有限的时空穿越操作。大雄他们需要从现在出发&#xff0c;到达一个目标时间点进行探险&#xff0c;结束后再返回到现在&#xff0c;他们希望尽可能减少…

Linux——网络编程概述

Q&#xff1a;为什么要网络编程&#xff1f;A&#xff1a;进程间的通信是依赖于Linux内核的&#xff0c;只能适用于单机&#xff0c;而要实现联机&#xff0c;就要用网络编程&#xff0c;网络编程是面向多台设备间的通信网络编程两个重要概念&#xff1a;IP地址和端口&#xff…

微服务拆分之道

背景 微服务在最近几年大行其道&#xff0c;很多公司的研发人员都在考虑微服务架构&#xff0c;同时&#xff0c;随着 Docker 容器技术和自动化运维等相关技术发展&#xff0c;微服务变得更容易管理&#xff0c;这给了微服务架构良好的发展机会。 在做微服务的路上&#xff0…

JavaWeb | JDBC相关API详解 1(含源码阅读)

本专栏主要是记录学习完JavaSE后学习JavaWeb部分的一些知识点总结以及遇到的一些问题等&#xff0c;如果刚开始学习Java的小伙伴可以点击下方连接查看专栏 本专栏地址&#xff1a;&#x1f525;JDBC Java入门篇&#xff1a; &#x1f525;Java基础学习篇 Java进阶学习篇&#x…

5、常量与变量

目录 1.数值型变量 &#xff08;1&#xff09;整型常量 &#xff08;2&#xff09;实型常量 2.字符型常量 (1)字符常量 (2) 字符串常量 3. ASCII表 4. 转义符号 5.符号常量 6. 整型变量 &#xff08;1&#xff09;有符号基本整型 &#xff08;2&#xff09;无符号基…

JavaScript Break 和 Continue 语句

文章目录JavaScript Break 和 Continue 语句Break 语句Continue 语句JavaScript 标签笔记列表JavaScript Break 和 Continue 语句 break 语句用于跳出循环。 continue 用于跳过循环中的一个迭代。 Break 语句 我们已经在本教程之前的章节中见到过 break 语句。它用于跳出 swi…

final的一个重要用途-宏变量和未初始化问题

/*** author 张家琛* version 1.0* date 2023/1/24 20:23*/ public class FinalDemo {public static void main(String[] args) {final var a5;System.out.println(a);} } 对于上面的程序来说&#xff0c;变量a其实根本就不存在&#xff0c;这段代码本质上执行的是转换成的&am…