概念解释

栈 stack,相当于一个开放的容器。先存储的元素位于栈底位置,后存储的元素位于栈顶位置。因此栈内存放的数据,先进入的数据后出栈,后存入的数据先出栈,在同一端进行插入和删除操作,遵循先进后出,后进先出原则(LIFO:Last In First Out)。
存入数据:进栈、压栈 push
取出数据:出栈、弹栈 pop
【示例】一
比如我们生活中经常用到的浏览器的返回与前进,就类似栈结构的先进后出:我们依次搜索leetcode、羽毛球筒、渡一、算法,它们会依次存放在一个开放容器中,当我们点击返回时会倒序弹出。
【栈·生活示例】.jpg
【示例】二
比如我们程序中一个方法调用另一个方法,需要等待后者执行结束的返回结果去做下一步的处理,这样的逻辑实现也是借助于栈结构。当我们调用方法一时,是把方法一压入了栈中;当方法一中调用了方法二时,又重新把方法二压入栈中,方法二执行结束后会弹出,继续执行方法一。可见,栈具有可记忆的特性。
function test1(){
int result = functionTest2();
result++;
…..
}


相关算法

一、有效的括号

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

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-parentheses

解法①:键值对存入匹配情况

【分析】通过栈来记录出现的字符,如果是右括号,匹配栈顶元素能否匹配,能匹配上就抵消,匹配不上就无效。如果是左括号,直接将左括号压入栈中。当遍历完成之后如果剩余一个空栈,说明所有的括号都已经匹配上了,如果有剩余的括号,说明有左括号没有被匹配上。

  1. public static boolean isValid(String s){
  2. HashMap<Character, Character> map = new HashMap<>();
  3. map.put('(',')');
  4. map.put('[',']');
  5. map.put('{','}');
  6. Stack<Character> stack = new Stack<>();
  7. char[] arr = s.toCharArray();
  8. for (char c : arr) {
  9. if (map.containsKey(c)){
  10. stack.push(c);
  11. }else {
  12. if (stack.isEmpty()){
  13. System.out.println("右括号出现,但是左括号尚未出现");
  14. return false;
  15. }
  16. Character top = stack.peek();
  17. if (!map.get(top).equals(c)){
  18. System.out.println("右括号出现,但是没有与之匹配的左括号");
  19. return false;
  20. }
  21. stack.pop();
  22. }
  23. }
  24. if (!stack.empty()){
  25. System.out.println("左括号出现,但没有右括号与之抵消");
  26. return false;
  27. }
  28. return true;
  29. }

解法②:栈存入抵消情况

【分析】:判断是否为左括号,是的话直接压栈右括号。如果为右括号,直接取栈顶元素匹配

  1. public static boolean isValid1(String s){
  2. Stack<Character> stack = new Stack<>();
  3. for (char c : s.toCharArray()) {
  4. if ('(' == c){
  5. stack.push(')');
  6. }else if ('[' == c){
  7. stack.push(']');
  8. }else if ('{' == c){
  9. stack.push('}');
  10. }else if (stack.empty() || c != stack.pop()){
  11. return false;
  12. }
  13. }
  14. return stack.empty();
  15. }

二、栈的多种实现方式

按照实际存储数据方式不同,栈可以分为顺序栈和链栈,分别用数组和链表存储数据。

方式①:基于数组的实现(顺序栈)

  1. public class MyStack {
  2. //盛放数据的数组
  3. int[] array;
  4. //栈的最大容量
  5. int maxSize;
  6. //栈顶元素的下标,即实际存储大小
  7. int top;
  8. public MyStack(int size){
  9. maxSize = size;
  10. array = new int[size];
  11. top = -1;
  12. }
  13. public void push(int value){
  14. if (top < maxSize - 1){
  15. top++;
  16. array[top] = value;
  17. }
  18. }
  19. public int pop(){
  20. int result = array[top];
  21. top--;
  22. return result;
  23. }
  24. public int peek(){
  25. return array[top];
  26. }
  27. public boolean isEmpty(){
  28. return top == -1;
  29. }
  30. public boolean isFull(){
  31. return top == (maxSize -1);
  32. }
  33. }

方式②:基于队列的实现

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

实现 MyStack 类:

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/implement-stack-using-queues

解法①:使用临时队列

【分析】
队列是先进先出,栈是先进后出。思路为使用临时的额外队列存储队列内元素,直到遍历到队尾元素输出。操作之后再将临时队列里的元素放回队列中,依此循环。

  1. public class MyStackByQueue {
  2. //存储数据的队列
  3. Queue<Integer> dataQueue = new LinkedList<>();
  4. //临时队列
  5. Queue<Integer> tmpQueue = new LinkedList<>();
  6. //栈顶元素
  7. int top;
  8. public void push(int x){
  9. dataQueue.add(x);
  10. top = x;
  11. }
  12. public int pop(){
  13. while (dataQueue.size() > 1){
  14. top = dataQueue.poll();
  15. tmpQueue.add(top);
  16. }
  17. Integer num = dataQueue.poll();
  18. Queue<Integer> tmp = dataQueue;
  19. dataQueue = tmpQueue;
  20. tmpQueue = tmp;
  21. return num;
  22. }
  23. public int top(){
  24. return top;
  25. }
  26. public boolean isEmpty(){
  27. return dataQueue.size() == 0;
  28. }
  29. }

解法②:对换队首队尾元素顺序

【分析】不使用额外的临时队列,每次存入数据后都对换队首队尾元素顺序,保证每次取出数据符合栈结构的取出顺序。

  1. public class MyStackByQueue2 {
  2. Queue<Integer> queue = new LinkedList<>();
  3. public void push(int x){
  4. int size = queue.size();
  5. queue.add(x);
  6. while (size > 1){
  7. Integer tail = queue.poll();
  8. queue.offer(tail);
  9. size--;
  10. }
  11. }
  12. public int pop(){
  13. return queue.poll();
  14. }
  15. public int top(){
  16. return queue.peek();
  17. }
  18. public boolean isEmpty(){
  19. return queue.isEmpty();
  20. }
  21. }

三、栈中值最小的元素

解法①:使用额外栈

【分析】:使用额外的栈记录栈中出现的最小元素,每次push新元素时,拿出临时栈栈顶元素进行对比,如果新元素大于临时栈顶元素,将最小元素重新push进临时栈一次,如果小于临时栈栈顶元素,就将该新元素push进临时栈。
【描述】:
输入:
[“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.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/min-stack

  1. public class MinStack {
  2. //数据栈
  3. Stack<Integer> dataStack = new Stack<>();
  4. //存储最小值的额外栈
  5. Stack<Integer> minStack = new Stack<>();
  6. public MinStack(){
  7. }
  8. public void push(int x){
  9. dataStack.push(x);
  10. if (!minStack.isEmpty() && minStack.peek() < x){
  11. minStack.push(minStack.peek());
  12. }else {
  13. minStack.push(x);
  14. }
  15. }
  16. public void pop(){
  17. dataStack.pop();
  18. minStack.pop();
  19. }
  20. public int top(){
  21. return dataStack.peek();
  22. }
  23. public int getMin(){
  24. return minStack.peek();
  25. }
  26. }

解法②:在同一栈中重复记录

public class MinStack1 {

    Stack<Integer> dataStack = new Stack<>();
    int min = Integer.MAX_VALUE;

    public void push(int x){
        //如果新存入的元素使最小值发生了变化,则存入两个元素(原来的最小值和新值)
        if (min >= x){
            //如果栈为空,说明是第一个元素,此时一定min > x
            if (!dataStack.isEmpty()){
                dataStack.push(min);
            }
            //最小值重新赋值
            min = x;
        }
        dataStack.push(x);
    }

    public void pop(){
        if (dataStack.isEmpty()) {return;}
        if (dataStack.size() == 1){
            min = Integer.MAX_VALUE;
        }else if (min == dataStack.peek()){
            dataStack.pop();
            min = dataStack.peek();
        }
        dataStack.pop();
    }

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

    public int getMin(){
        return min;
    }
}

四、下一个最大值

【描述】:
nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。
返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-greater-element-i
【示例】:
输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:

  • 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
  • 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。
  • 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。

    解法:

    【分析】暂时忽略nums1,直接求取nums2中每一个元素的下一个最大值
    【栈实现数组内元素的下一个最大元素】.jpg

    public static int[] nextGreaterNum(int[] nums1, int[] nums2){
          //临时栈
          Stack<Integer> stack = new Stack<>();
          HashMap<Integer, Integer> map = new HashMap<>();
    
          for (int i = 0; i < nums2.length; i++) {
              while ( !stack.isEmpty() && stack.peek() < nums2[i]){
                  map.put(stack.pop(),nums2[i]);
              }
              stack.push(nums2[i]);
          }
          while (!stack.isEmpty()){
              map.put(stack.pop(),-1);
          }
          int[] result = new int[nums1.length];
          for (int i = 0; i < nums1.length; i++) {
              result[i] = map.get(nums1[i]);
          }
          return result;
      }
    

    五、经典应用之逆波兰表达式

    类型一、后缀

    【描述】
    根据 逆波兰表示法,求表达式的值。
    有效的算符包括 +、-、、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
    注意 两个整数之间的除法只保留整数部分。
    可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
    示例 1:
    输入:tokens = [“2”,”1”,”+”,”3”,”
    “]
    输出:9
    解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/evaluate-reverse-polish-notation
【分析】
逆波兰表达式是自带优先级的,出现数字就存入栈中,出现运算符就取出栈中的两个数值进行运算符对应的运算,并将运算结果再次入栈。
[“2”,”1”,”+”,”3”,”“]
stack[2,1] -> +
stack[3]
stack[3,3] ->

stack[9]
【代码实现】:

public class ReversePolishNotation {

    public static void main(String[] args) {
        String[] tokens = {"2","3","+","5","*"};
        System.out.println(evalRPN(tokens));
    }

    public static int evalRPN(String[] tokens){
        Stack<String> stack = new Stack<String>();
        int nums1,nums2;
        for (int i = 0; i < tokens.length; i++) {
            switch (tokens[i]){
                case "+":
                    nums1 = Integer.parseInt(stack.pop());
                    nums2 = Integer.parseInt(stack.pop());
                    stack.push(nums2 + nums1 + "");
                    break;
                case "-":
                    nums1 = Integer.parseInt(stack.pop());
                    nums2 = Integer.parseInt(stack.pop());
                    stack.push(nums2 - nums1 + "");
                    break;
                case "*":
                    nums1 = Integer.parseInt(stack.pop());
                    nums2 = Integer.parseInt(stack.pop());
                    stack.push(nums2 * nums1 + "");
                    break;
                case "/":
                    nums1 = Integer.parseInt(stack.pop());
                    nums2 = Integer.parseInt(stack.pop());
                    stack.push(nums2 / nums1 + "");
                    break;
                default:stack.push(tokens[i]);

            }

        }

        return Integer.parseInt(stack.pop());
    }
}

类型二:中缀表达式

声明两个栈结构S1,和S2分别存入