• [java]快速入门栈和队列,手撕相关面试题


    专栏简介 :java语法及数据结构

    题目来源:leetcode,牛客,剑指offer

    创作目标:从java语法角度实现底层相关数据结构,达到手撕各类题目的水平.

    希望在提升自己的同时,帮助他人,与大家一起共同进步,互相成长.

    学历代表过去,能力代表现在,学习能力代表未来!


    目录

    前言

    一.栈的定义与实现

    1)栈的定义:

     2)栈常见操作方法:

     3)栈的实现:

    二.栈相关题目

    1)逆波兰数

    2)不可能的入栈方式

    3)有效括号

    4)最小栈

    三.队列的实现

    1)单链表实现队列

    2)循环队列

    四.队列相关题目

    1)用栈实现队列 

    2)用队列实现栈

    总结


    前言

            Hello!大家好!我是Node_Hao,今天给大家带来的是栈和队列的底层实现及其构造方法,旨在熟练掌握栈和队列的使用以后,可以手撕各类栈相关的题目.希望我的文章能对你有所帮助与启发!


    一.栈的定义与实现

    1)栈的定义:

          栈是一种数据结构特点是"先进后出",基于这一特点栈不管是push()(压入元素)还是pop()(弹出栈顶元素),时间复杂度都是O(1).而java虚拟机栈只是JVM中的一块内存,用来存放局部变量..... 调用函数时我们会在java虚拟机栈中开辟一块内存叫栈帧.

     

      

     2)栈常见操作方法:

    1.stack.empty().判断栈是否为空,为空就返回false,否则返回true.

    2.stack.push().将元素压入栈底.

    3.stack.pop().将栈顶元素弹出栈.

    4.satck.peek().peek有窥视的意思,顾名思义作用就是查看栈顶元素,但不弹出.

     3)栈的实现:

            栈的底层实现既可以用顺序表也可以使用双向链表,二者实现方式大同小异,而我们使用的是顺序表.基本配置只需要一个数组来存放元素和一个usedSize记录顺序表元素的个数.构造方法初始化时我们可以将栈的大小初始化为5,后续不够再扩容.扩容使用Arrays.copyof()方法.

    1. class My_stack{
    2. int[] elem;
    3. int usedSize;
    4. public My_stack(int[] elem, int usedSize) {
    5. this.elem = new int[5];
    6. this.usedSize = usedSize;
    7. }
    8. public void push(int val){
    9. if (usedSize==elem.length){//如果满了就扩容
    10. Arrays.copyOf(elem,elem.length*2);
    11. }
    12. elem[usedSize] = val;
    13. usedSize++;
    14. }
    15. public int pop(){
    16. if (isEmpty()){
    17. throw new RuntimeException("栈为空");
    18. }
    19. return elem[usedSize--];
    20. }
    21. public int peek(){
    22. if (isEmpty()){
    23. throw new RuntimeException("栈为空");
    24. }
    25. return elem[usedSize-1];
    26. }
    27. public boolean isEmpty(){
    28. return usedSize==0;
    29. }
    30. }

    二.栈相关题目

    1)逆波兰数

            逆波兰数也叫后缀表达式,早年计算机并没有用括号来规定四则运算的计算顺序,那么如果计算9+(3-1)x3+10/2总不能直接输9+3-1x3+10/2吧,于是睿智的科学家通过栈解决了这个难题,栈中存放的只能是数字,所以只要遇到数字就压栈,遇到运算符号就从栈顶弹出两个数字,最顶部的在操作符右边,下一个在操作符左边,运算完后将结果压栈,继续重复上述操作.

            在此题的基础上我们拓展中缀表达式转后缀表达式,首先按四则运算的优先级给表达式加上相应的括号((9+((3-1)x3))+(10/2))然后将每个括号中对应的操作符移到该括号之外:

    (((9((31)-3)x)+)10 2)/)+去掉括号后得:931-3x+102/+.

     

    1. class Solution {
    2. public int evalRPN(String[] tokens) {
    3. Stack str = new Stack<>();
    4. for(int i = 0;i
    5. String s = tokens[i];
    6. if(!isoperation(s)){
    7. str.push(Integer.parseInt(s));//将数字压栈
    8. }else{
    9. int num2 = str.pop();
    10. int num1 = str.pop();
    11. switch(s){
    12. case "+":
    13. str.push(num1+num2);
    14. break;
    15. case "-":
    16. str.push(num1-num2);
    17. break;
    18. case "*":
    19. str.push(num1*num2);
    20. break;
    21. case "/":
    22. str.push(num1/num2);
    23. break;
    24. }
    25. }
    26. }
    27. return str.pop();
    28. }
    29. public boolean isoperation(String s){
    30. if(s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/")){
    31. return true;
    32. }
    33. return false;
    34. }
    35. }

    2)不可能的入栈方式

             根据栈"先进后出"的原理我们可以将pushed数组,每压入一个元素就与popped数组的栈顶元素相比较,如果相同就弹出压入数组的元素,然后访问popped数组的下一个元素继续比较.如果不相同,pushed数组继续压栈,重复上述操作.当pushed数组全部压入后,如果此时栈为空,那么符合出栈方式,如果不为空则不符合.

     

    1. class Solution {
    2. public boolean validateStackSequences(int[] pushed, int[] popped) {
    3. Stack stack = new Stack();
    4. int j = 0;
    5. for(int i = 0;i
    6. stack.push(pushed[i]);
    7. while(!stack.empty()&&i
    8. stack.pop();
    9. j++;
    10. }
    11. }
    12. if(!stack.empty()){
    13. return false;
    14. }
    15. return true;
    16. }
    17. }

    3)有效括号

             通过了解题目大致有四种情况,1.左边括号多( ( ( ) 2.右边括号多( ( ) ) ) ) 3.左右括号不匹配{ ) [ } 4.左右括号匹配( ) { } .那么根据栈的特点,我们可以遇到左边的括号就压入栈中,遇到右边的括号便和栈顶的元素比较是否匹配,如果不匹配返回false.但要注意两点:1. 如果我们在比较的过程中发现栈为空,那么就是右边括号多,返回fasle.2.如果我们比较完毕,栈不为空,说明左边括号多,返回false.如果没有以上情形我们就可以返回true.

     

    1. class Solution {
    2. public boolean isValid(String s) {
    3. Stack stack = new Stack<>();
    4. for(int i = 0;i
    5. char ch = s.charAt(i);
    6. if(ch=='('||ch=='['||ch=='{'){
    7. stack.push(ch);
    8. }else{
    9. if(stack.empty()){//右边括号多
    10. return false;
    11. }
    12. char top = stack.peek();
    13. if((top=='('&&ch==')')||(top=='['&&ch==']')||(top=='{'&&ch=='}')){
    14. stack.pop();
    15. }else{
    16. return false;//左右括号不匹配
    17. }
    18. }
    19. }
    20. if(!stack.empty()){//左面括号多
    21. return false;
    22. }
    23. return true;
    24. }
    25. }

    4)最小栈

            要想实现在常数时间内检索到最小元素的栈,那么一定不可能一个栈,因为根据栈的特点检索元素的时间复杂度必然是O(N),所以如果我们可以把栈的最小元素单独保存起来,那么当我们需要时查找最小元素时间复杂度必然是O(1).按照上述思路,首先我们创建两个栈,第一个栈中存放元素,第二个栈中存放最小值.其次压入元素时,如果是第一次压入,两个栈都压.否则判断要压入的元素是否小于第二个栈栈顶的元素,如果小于就压入第二个栈,否则就不压入.重复上述操作,无论任何时候我们都能按O(1)的时间复杂度取出第二个栈栈顶的元素,也就是最小的元素.

    1. class MinStack {
    2. public Stack stack1;
    3. public Stack stack2;
    4. public MinStack() {
    5. stack1 = new Stack();
    6. stack2 = new Stack();
    7. }
    8. public void push(int val) {
    9. stack1.push(val);
    10. if (stack2.empty()){
    11. stack2.push(val);
    12. }else {
    13. if(val<=stack2.peek()){
    14. stack2.push(val);
    15. }
    16. }
    17. }
    18. public void pop() {
    19. int popVal = stack1.pop();
    20. if(!stack2.empty()){
    21. int top = stack2.peek();
    22. if(top==popVal){
    23. stack2.pop();
    24. }
    25. }
    26. }
    27. public int top() {
    28. return stack1.peek();
    29. }
    30. public int getMin() {
    31. return stack2.peek();
    32. }
    33. }

    三.队列的实现

            队列是只允许在一端进行插入另一端进行删除的线性表,与栈正好相反.简要概括为:"尾进头出",同样队列的实现底层既可以用顺序表也可以用链表.首先我们用单链表来实现,为了使入队和出队的时间复杂度都为O(1),我们需要记录单链表的头结点和尾结点,入队时尾插,出队时删除头结点即可.

    1)单链表实现队列

    1. class Node1{
    2. public Node1 next;
    3. public int val;
    4. public Node1(int val) {
    5. this.val = val;
    6. }
    7. }
    8. class My_queue{
    9. public Node1 head;
    10. public Node1 last;
    11. public void offer(int val){//入队
    12. Node1 node1 = new Node1(val);
    13. if (head==null){
    14. head = node1;
    15. last = node1;
    16. }else {
    17. last.next = node1;
    18. last = last.next;
    19. }
    20. }
    21. public int poll(){//出队
    22. if (isEmpty()){
    23. throw new RuntimeException("队列为空");
    24. }
    25. int pollVal = head.val;
    26. head = head.next;
    27. return pollVal;
    28. }
    29. public int peek(){
    30. if (isEmpty()){
    31. throw new RuntimeException("队列为空");
    32. }
    33. return head.val;
    34. }
    35. public boolean isEmpty(){
    36. return head==null;
    37. }
    38. }

    2)循环队列

            循环队列的底层为顺序表,为了使出队和入队的时间复杂度都是O(1),我们必须记录顺序表元素的首尾,rear为队尾控制元素入队,front为队首控制元素出队. 为了使顺序表达到循环的效果,我们需要借助公式(rear+1)%elem.length,front也同样借助这个公式.基于顺序表的特点,删除元素时只需后移front,增加元素只需在rear下标增加元素,之后后移rear即可.

    1. class MyCircularQueue {
    2. public int elem[];
    3. public int front;
    4. public int rear;
    5. public MyCircularQueue(int k) {
    6. this.elem = new int[k+1];
    7. }
    8. public boolean enQueue(int value) {//入队
    9. if (isFull()){
    10. return false;
    11. }
    12. this.elem[rear] = value;
    13. rear = (rear +1)%elem.length;
    14. return true;
    15. }
    16. public boolean deQueue() {//出队
    17. if (isEmpty()){
    18. return false;
    19. }
    20. front = (front +1)%elem.length;
    21. return true;
    22. }
    23. public int Front() {//获取队头元素
    24. if (isEmpty()){
    25. return -1;
    26. }
    27. return elem[front];
    28. }
    29. public int Rear() {//获取队位元素
    30. if (isEmpty()){
    31. return -1;
    32. }
    33. if (rear==0){
    34. return elem[elem.length-1];
    35. }else {
    36. return elem[rear-1];
    37. }
    38. }
    39. public boolean isEmpty() {
    40. return front==rear;
    41. }
    42. public boolean isFull() {
    43. //rear的下一个如果为front
    44. if ((rear+1)%elem.length==front){
    45. return true;
    46. }
    47. return false;
    48. }
    49. }

    四.队列相关题目

    1)用栈实现队列 

            一个栈必定无法实现队列,那么我们可以考虑用两个栈,入队时把要入队元素压入stack1中,出队时先把所有stack1中元素压入stack2中,然后弹出stack2的栈顶元素即可

    1. class MyQueue {
    2. public Stack stack1;
    3. public Stack stack2;
    4. public MyQueue() {
    5. stack1 = new Stack<>();
    6. stack2 = new Stack<>();
    7. }
    8. public void push(int x) {
    9. stack1.push(x);
    10. }
    11. public int pop() {
    12. if(empty()){
    13. return -1;
    14. }
    15. if(stack2.empty()) {
    16. int size = stack1.size();
    17. for (int i = 0; i < size; i++) {
    18. stack2.push(stack1.pop());
    19. }
    20. }
    21. return stack2.pop();
    22. }
    23. public int peek() {
    24. if(empty()){
    25. return -1;
    26. }
    27. if(stack2.empty()) {
    28. int size = stack1.size();
    29. for (int i = 0; i < size; i++) {
    30. stack2.push(stack1.pop());
    31. }
    32. }
    33. return stack2.peek();
    34. }
    35. public boolean empty() {
    36. return stack1.empty()&&stack2.empty();
    37. }
    38. }

    2)用队列实现栈

            同样一个队列也无法实现栈,可以考虑使用两个队列,入队时哪个队列为空入哪个 ,如果都为空就入que1.但是与栈不同的是队列的出队顺序,我们不能把que1中的元素全部放入que2再出que2中的元素,而是移size-1个到que2,出que1.假设入队都在que1中,那么出队时只需将que1中的size-1个元素移到que2中,然后弹出que1中的元素即可.

    1. class MyStack {
    2. public Queue que1;
    3. public Queue que2;
    4. public MyStack() {
    5. que1 = new LinkedList<>();
    6. que2 = new LinkedList<>();
    7. }
    8. public void push(int x) {
    9. if (!que1.isEmpty()){
    10. que1.offer(x);
    11. }else if (!que2.isEmpty()){
    12. que2.offer(x);
    13. }else {
    14. que1.offer(x);
    15. }
    16. }
    17. public int pop() {
    18. if (empty()){
    19. return -1;
    20. }
    21. if (!que1.isEmpty()){
    22. int size = que1.size();
    23. for (int i = 0; i 1 ; i++) {
    24. que2.offer(que1.poll());
    25. }
    26. return que1.poll();
    27. }
    28. if (!que2.isEmpty()){
    29. int size = que2.size();
    30. for (int i = 0; i 1 ; i++) {
    31. que1.offer(que2.poll());
    32. }
    33. return que2.poll();
    34. }
    35. return -1;
    36. }
    37. public int top() {
    38. if (empty()){
    39. return -1;
    40. }
    41. if (!que1.isEmpty()){
    42. int size = que1.size();
    43. int val = -1;
    44. for (int i = 0; i
    45. val = que1.poll();
    46. que2.offer(val);
    47. }
    48. return val;
    49. }
    50. if (!que2.isEmpty()){
    51. int size = que2.size();
    52. int val = -1;
    53. for (int i = 0; i
    54. val = que2.poll();
    55. que1.offer(val);
    56. }
    57. return val;
    58. }
    59. return -1;
    60. }
    61. public boolean empty() {
    62. return que1.isEmpty()&&que2.isEmpty();
    63. }
    64. }


    总结

            以上就是快速入门栈和队列的全部内容了,在了解栈和队列的各种实现并手撕相关习题,相信我们已经熟练掌握了栈和队列,如果我的文章对你有亿点点帮助和启发,麻烦不要忘记三连哦!

  • 相关阅读:
    UNCTF-日常训练-reverse-easyMaze
    spring cloud 小试牛刀
    【头歌-Python】8.3 政府工作报告数据提取(project)-第4关
    7 款好用的 PDF 密码删除工具
    R语言生物群落数据统计分析
    计算机网络期末知识点(第二章-物理层)
    Qt 在linux上检测内存泄漏,用valgrind的问题
    AirServer最新Win64位个人版投屏软件
    浅析“代码可视化” | 京东云技术团队
    ros 常用是命令
  • 原文地址:https://blog.csdn.net/liu_xuixui/article/details/126091883