• 数据结构:栈


    特点

    先进后出(FILO)后进先出(LIFO)

    现在我有一个栈,栈顶不封顶,可以将元素加进来,但是栈底是封闭的,元素没办法溜走栈底 

    把元素加进去,想要把12拿出来的时候,必须让23和34分别出来,12才能从栈底脱身


    手搓一个栈

    我们先用数组来实现一个栈,它的时间复杂度是O(1)

    🆗了解了这些方法之后,我们把这些方法写到接口里面

    1. //IStack 接口
    2. package stack;
    3. public interface IStack {
    4. void push(int x);
    5. int pop();
    6. int peek();
    7. int size();
    8. boolean empty();
    9. boolean full();
    10. }

    在具体实现的文件里面实现方法

    1. package stack;
    2. public class MyStack implements IStack{
    3. //初始化
    4. private int[] elem;
    5. private int usedSize;
    6. private static final int DEFAULT_CAPACITY = 10;
    7. public MyStack(){
    8. elem = new int[DEFAULT_CAPACITY];
    9. }
    10. @Override
    11. public void push(int x) {
    12. }
    13. @Override
    14. public int pop() {
    15. return 0;
    16. }
    17. @Override
    18. public int peek() {
    19. return 0;
    20. }
    21. @Override
    22. public int size() {
    23. return 0;
    24. }
    25. @Override
    26. public boolean empty() {
    27. return false;
    28. }
    29. @Override
    30. public boolean full() {
    31. return false;
    32. }
    33. }

    push 压栈

    压栈之前首先要判断栈是不是满的

    1. @Override
    2. public boolean full() {
    3. if(usedSize == elem.length){
    4. return true;
    5. }
    6. return false;
    7. }

    满了的话就需要扩容,然后把数组对应位置放入值x就行

    1. @Override
    2. public void push(int x) {
    3. if (full()){
    4. elem = Arrays.copyOf(elem, 2* elem.length);
    5. }
    6. elem[usedSize] = x;
    7. usedSize++;
    8. }

    pop出栈

    弹出元素需要先判断栈内为不为空

    1. @Override
    2. public boolean empty() {
    3. return usedSize == 0;
    4. }

    如果为空了,可以抛一个异常

    1. if(empty()){
    2. throw new EmptyException("栈空了!")
    3. }
    4. //重新写一个文件反馈异常
    5. public class EmptyException extends RuntimeException{
    6. public EmptyException(String msg){
    7. super(msg);
    8. }
    9. }

    删除元素 --》usedSize--  就行了

    1. @Override
    2. public int pop() {
    3. if(empty()){
    4. throw new EmptyException("栈空了!")
    5. }
    6. int old = elem[usedSize-1];
    7. usedSize--;//相当于删除元素
    8. return old;
    9. }

    peek和size

    1. @Override
    2. public int peek() {
    3. return elem[usedSize-1];
    4. }
    5. @Override
    6. public int size() {
    7. return usedSize;
    8. }

    用链表实现一个栈

    如果是单链表,我们没有last这个引用

    假设从头入栈-》O(1),从头出:删除头节点 -》O(1)

    假设从尾巴入栈 --》 O(n)   从尾巴出--》O(n)

    如果是双向链表,可以直接当成一个栈,叫做链式栈


    有关栈的题目和应用

    开胃小菜

    1. 若进栈序列为 1,2,3,4 进栈过程中可以出栈,则下列不可能的一个出栈序列是()
    A: 1,4,3,2 B: 2,3,4,1 C: 3,1,4,2 D: 3,4,2,1

    answer:C

    要先弹出3,栈里面必然有1和2,然而当3弹出来后,1被2挡着,不可能比2更快弹出

    所以C就是不对的

    看看B

    1和2先入栈,2先出栈,再入栈3,3出栈,再入栈4,4出栈最后1出栈

    顺序就是2,3,4,1

    2. 一个栈的初始状态为空。现将元素 1 2 3 4 5 A B C D E 依次入栈,然后再依次出栈,则元素出栈的顺序是( B)。
    A: 12345ABCDE B: EDCBA54321 C: ABCDE12345 D: 54321EDCBA

    3.应用栈来逆序打印链表

    循环方法

    用一个cur遍历链表,每次遍历一个元素就加入栈中,把元素一一出栈,出栈的过程中顺便打印

    递归方法

    比循环多这么一步:每次入栈就调用一次自己的方法

    1. // 递归方式
    2. void printList(Node head){
    3. if(null != head){
    4. printList(head.next);
    5. System.out.print(head.val + " ");
    6. }
    7. }
    8. // 循环方式
    9. void printList(Node head){
    10. if(null == head){
    11. return;
    12. }
    13. Stack s = new Stack<>();
    14. // 将链表中的结点保存在栈中
    15. Node cur = head;
    16. while(null != cur){
    17. s.push(cur);
    18. cur = cur.next;
    19. }
    20. // 将栈中的元素出栈
    21. while(!s.empty()){
    22. System.out.print(s.pop().val + " ");
    23. }
    24. }

    150. 逆波兰表达式求值 - 力扣(LeetCode)

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

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

    注意:

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

    示例 1:

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

    示例 2:

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

    示例 3:

    输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
    输出:22
    解释:该算式转化为常见的中缀算术表达式为:
      ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
    = ((10 * (6 / (12 * -11))) + 17) + 5
    = ((10 * (6 / -132)) + 17) + 5
    = ((10 * 0) + 17) + 5
    = (0 + 17) + 5
    = 17 + 5
    = 22
    

    提示:

    • 1 <= tokens.length <= 104
    • tokens[i] 是一个算符("+""-""*" 或 "/"),或是在范围 [-200, 200] 内的一个整数

    什么叫做逆波兰表达式

    逆波兰表达式是一种后缀表达式

    我们平常写的算式是中缀表达式 比如:9+(3-1)*3+8/2

    改成后缀表达式就是 9 3 1 - 3 * + 8 2 / +

    中缀怎么变后缀的呢?

    我们的中缀表达式遵循先乘除后加减的原则

    我们就先给乘除操作加上一个括号

    9先加上乘法的结果,再给他们俩一个括号

    最后整个加在一起,再给整体一个括号

    现在把每个运算符移到对应括号的外边,注意只用移动一层就行

    然后把所有的括号删掉就是后缀表达式了

    9 3 1 - 3 * + 8 2 / +

    那后缀表达式怎么运行/计算的呢?

    1.把符号前面数字扔到栈里面,符号不要扔进去!

    2.现在栈里面有9,3和1,把先弹出来的1放到减号的右边,紧接着弹出来的3放到减号左边

    3.3-1 = 2 把这个计算得出的2再压回栈中

    现在栈里面长这样

    4.再把3扔进去

    5.弹出3放*右边,弹出2放*左边,计算得到的6再压入栈里面

    6.加号放栈外边,依次弹出6和9,计算得到15再压入栈

    7.把8和2依次压入栈,然后2和8依次弹出分别放除号右边和左边 

    8.计算得到4再压回栈里

    现在栈的情况

     

    最后把这俩依次弹出放到+右边和左边再进行计算得到19,这个19就是最终的答案啦

    🆗了解了后缀表达式怎么计算,那我们回到题目

    这道题就是要我们用代码实现上面的过程

    第一步:我们把数字(Integer)压入栈中,同时要设计一个方法判断是不是Integer

    1. private boolean isOperation(String s){
    2. if(s.equals("+") || s.equals("-")||s.equals("*")||s.equals("/")){
    3. return true;
    4. }
    5. return false;
    6. }
    1. Stack stack = new Stack<>();
    2. for(String x:tokens){
    3. if(!isOperation(x)){
    4. stack.push(Integer.parseInt(x));//字符串转整型

    第二步:处理四个字符串

    1. else{
    2. int num2 = stack.pop();
    3. int num1 = stack.pop();//注意先弹出num2,方便后面把num2放到右边
    4. switch(x){
    5. case "+":
    6. stack.push(num1+num2);
    7. break;
    8. case "-":
    9. stack.push(num1-num2);
    10. break;
    11. case "*":
    12. stack.push(num1*num2);
    13. break;
    14. case "/":
    15. stack.push(num1/num2);
    16. break;
    17. }
    18. }

    第三步:把最终的计算结果pop出来就行

    整个的代码

    1. class Solution {
    2. public int evalRPN(String[] tokens) {
    3. Stack stack = new Stack<>();
    4. for(String x:tokens){
    5. if(!isOperation(x)){
    6. stack.push(Integer.parseInt(x));//字符串转整型
    7. }else{
    8. int num2 = stack.pop();
    9. int num1 = stack.pop();//注意先弹出num2,方便后面把num2放到右边
    10. switch(x){
    11. case "+":
    12. stack.push(num1+num2);
    13. break;
    14. case "-":
    15. stack.push(num1-num2);
    16. break;
    17. case "*":
    18. stack.push(num1*num2);
    19. break;
    20. case "/":
    21. stack.push(num1/num2);
    22. break;
    23. }
    24. }
    25. }
    26. return stack.pop();
    27. }
    28. private boolean isOperation(String s){
    29. if(s.equals("+") || s.equals("-")||s.equals("*")||s.equals("/")){
    30. return true;
    31. }
    32. return false;
    33. }
    34. }


    20. 有效的括号 - 力扣(LeetCode) 

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

    有效字符串需满足:

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

    示例 1:

    输入:s = "()"
    输出:true
    

    示例 2:

    输入:s = "()[]{}"
    输出:true
    

    示例 3:

    输入:s = "(]"
    输出:false
    

    提示:

    • 1 <= s.length <= 104
    • s 仅由括号 '()[]{}' 组成

     括号不匹配的情况(我们只需要解决的情况)

    遇到的第一个右括号应该和最后一个左括号进行匹配,所以我们要用到栈

    先把左括号入栈,遇到右括号的时候和栈顶元素的左括号比较是不是匹配的,每匹配一个就pop一个

    当栈里面的元素为空且字符串也遍历完成时,匹配完成

    情况1:

    情况2:

    情况3:

    完整的代码


    栈的压入、弹出序列_牛客题霸_牛客网 (nowcoder.com) 

    描述

    输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。

    1. 0<=pushV.length == popV.length <=1000

    2. -1000<=pushV[i]<=1000

    3. pushV 的所有数字均不相同

    示例1

    输入:

    [1,2,3,4,5],[4,5,3,2,1]

    返回值:

    true

    说明:

    可以通过push(1)=>push(2)=>push(3)=>push(4)=>pop()=>push(5)=>pop()=>pop()=>pop()=>pop()
    这样的顺序得到[4,5,3,2,1]这个序列,返回true      

    示例2

    输入:

    [1,2,3,4,5],[4,3,5,1,2]

    返回值:

    false
    

    说明:

    由于是[1,2,3,4,5]的压入顺序,[4,3,5,1,2]的弹出顺序,要求4,3,5必须在1,2前压入,且1,2不能弹出,但是这样压入的顺序,1又不能在2之前弹出,所以无法形成的,返回false      

     

    我们设置两个数组,一个是压栈的数组,一个是用来判断的数组

    设置i遍历push里面的元素,每次遍历一个元素看超不超过pop里面的j位置的元素的大小

    设置j遍历pop里面的元素,遍历一个就看看与栈里面元素是不是一样的,一样的就把栈里面对应元素pop出来

     如图,pop第一个元素是4,那么push里面1 2 3 4可以直接push到栈里面,4一样,弹出,再把5push进去,j往后走。5一样,弹出。再依次比较3 2 1

    push和栈都是空的,说明匹配完成

    归纳步骤:

    1.遍历push数组,把元素放入栈中

    1. Stack stack = new Stack<>();
    2. int j = 0;
    3. for(int i = 0; i < pushV.length; i++){
    4. stack.push(pushV[i]);

    2.每push一个元素,就和pop数组的元素比较

    3.如果相等,j++且出栈(注意:这么做的前提是栈不为空且数组不越界)

    4.如果不相等,就想办法入栈

    1. while(!stack.empty() && j < popV.length && stack.peek() == popV[j]){
    2. stack.pop();
    3. j++;
    4. }

    完整代码


    155. 最小栈 - 力扣(LeetCode)

    设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

    实现 MinStack 类:

    • MinStack() 初始化堆栈对象。
    • void push(int val) 将元素val推入堆栈。
    • void pop() 删除堆栈顶部的元素。
    • int top() 获取堆栈顶部的元素。
    • int getMin() 获取堆栈中的最小元素。

    示例 1:

    输入:
    ["MinStack","push","push","push","getMin","pop","top","getMin"]
    [[],[-2],[0],[-3],[],[],[],[]]
    
    输出:
    [null,null,null,null,-3,null,0,-2]
    
    解释:
    MinStack minStack = new MinStack();
    minStack.push(-2);
    minStack.push(0);
    minStack.push(-3);
    minStack.getMin();   --> 返回 -3.
    minStack.pop();
    minStack.top();      --> 返回 0.
    minStack.getMin();   --> 返回 -2.

     这道题只用一个栈是明显行不通的

    假设有这么一组数,-1,2,6,3,依次入栈,想要最快拿到最小值-1就得经过O(n)时间,不合题意

    所以我们可以申请两个栈,一个栈就是普通的栈,用来放列表的元素的

    另一个栈就是最小栈,每次放入普通栈的元素都要和原来栈里面的元素进行比较,如果是最小的话就放入最小栈,放到最后你会发现,最小栈的栈顶就是那个最小值

    初始化

    1. private Stack stack;
    2. private Stack minStack;
    3. public MinStack() {
    4. stack = new Stack<>();
    5. minStack = new Stack<>();
    6. }

    push

    普通的栈一定得放元素

    最小栈如果是空的,也要放;如果不为空且要存放的元素小于最小栈的栈顶,也要放到最小栈

    1. public void push(int val) {
    2. //普通栈放元素
    3. stack.push(val);
    4. //最小栈空不空
    5. if(minStack.empty()){
    6. minStack.push(val);
    7. }else{
    8. //判断要存放的元素是否小于栈顶元素
    9. int peekVal = minStack.peek();
    10. //相同元素也要压入最小栈
    11. if(val<=peekVal){
    12. minStack.push(val);
    13. }
    14. }
    15. }

    pop

    1.要pop的元素和栈顶元素比较

    2.如果pop的元素和栈顶元素是一样的,那么两个栈都要出

    3.不一样只出普通栈

    1. public void pop() {
    2. int val = stack.pop();
    3. if(!minStack.empty()){
    4. if(val == minStack.peek()){
    5. minStack.pop();
    6. }
    7. }
    8. }

    top和getMin

    1. //peek获取当前普通栈的栈顶元素
    2. public int top() {
    3. return stack.peek();
    4. }
    5. //最小栈的peek,通过这个方法获取最小值
    6. public int getMin() {
    7. if(!minStack.empty()){
    8. return minStack.peek();
    9. }
    10. return -1;
    11. }

    概念区分

    栈、虚拟机栈、栈帧的区别

    栈:一种数据结构

    虚拟机栈:JVM划分的一块内存

    栈帧:调用方法的时候会在虚拟机当中给这块方法开辟一块内存

  • 相关阅读:
    技术选型思考:分库分表和分布式DB(TiDB/OceanBase) 的权衡与抉择
    戏说领域驱动设计(廿六)——再谈事务
    大珩PPT助手一键颜色设置
    java计算机毕业设计宠物店管理系统MyBatis+系统+LW文档+源码+调试部署
    6234. 最小公倍数为 K 的子数组数目
    Linux
    【C语言】指针的进阶(四)—— 企业笔试题解析
    Netty 基础详解
    说说 Redis 缓存删除策略
    JAVA毕设项目作业自动评阅系统的设计和开发(java+VUE+Mybatis+Maven+Mysql)
  • 原文地址:https://blog.csdn.net/hellg/article/details/133818747