栈-我的基础算法刷题之路(五)

在这里插入图片描述

本篇博客旨在整理记录自已对栈的一些总结,以及刷题的解题思路,同时希望可给小伙伴一些帮助。本人也是算法小白,水平有限,如果文章中有什么错误之处,希望小伙伴们可以在评论区指出来,共勉 💪。

栈的概述

栈(stack): 是一种线性的数据结构,也是一个 先入后出 的有序列表。只能在其一端添加数据和移除数据。习惯来说,这一端称之为栈顶,另一端不能操作数据的称之为栈底,就如同生活中的一摞书。

栈的基本操作

  • void push(int data):入栈(将数据data插入到栈中)
  • int pop():出栈(删除并返回最后一个插入栈的元素)
  • int top():返回最后一个插入栈的元素,但不删除
  • int size():返回存储在栈中的元素个数
  • boolean isEmpty():返回栈是否是空栈
  • boolean isFull():返回是否是满栈
  • void Clear():清除整个栈

实现栈的完整代码

首先提供一个栈接口:

public interface Stack<E> {
    /**
     * 向栈顶压入元素
     * @param value 待压入值
     * @return 压入成功返回 true, 否则返回 false
     */
    boolean push(E value);

    /**
     * 从栈顶弹出元素
     * @return 栈非空返回栈顶元素, 栈为空返回 null
     */
    E pop();

    /**
     * 返回栈顶元素, 不弹出
     * @return 栈非空返回栈顶元素, 栈为空返回 null
     */
    E peek();

    /**
     * 判断栈是否为空
     * @return 空返回 true, 否则返回 false
     */
    boolean isEmpty();

    /**
     * 判断栈是否已满
     * @return 满返回 true, 否则返回 false
     */
    boolean isFull();
}

链表实现栈

public class LinkedListStack<E> implements Stack<E>, Iterable<E> {

    private final int capacity;
    private int size;
    private final Node<E> head = new Node<>(null, null);

    public LinkedListStack(int capacity) {
        this.capacity = capacity;
    }

    @Override
    public boolean push(E value) {
        if (isFull()) {
            return false;
        }
        head.next = new Node<>(value, head.next);
        size++;
        return true;
    }

    @Override
    public E pop() {
        if (isEmpty()) {
            return null;
        }
        Node<E> first = head.next;
        head.next = first.next;
        size--;
        return first.value;
    }

    @Override
    public E peek() {
        if (isEmpty()) {
            return null;
        }
        return head.next.value;
    }

    @Override
    public boolean isEmpty() {
        return head.next == null;
    }

    @Override
    public boolean isFull() {
        return size == capacity;
    }

    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            Node<E> p = head.next;
            @Override
            public boolean hasNext() {
                return p != null;
            }

            @Override
            public E next() {
                E value = p.value;
                p = p.next;
                return value;
            }
        };
    }

    static class Node<E> {
        E value;
        Node<E> next;

        public Node(E value, Node<E> next) {
            this.value = value;
            this.next = next;
        }
    }
}

数组实现栈

public class ArrayStack<E> implements Stack<E>, Iterable<E>{
    private final E[] array;
    private int top = 0;

    @SuppressWarnings("all")
    public ArrayStack(int capacity) {
        this.array = (E[]) new Object[capacity];
    }

    @Override
    public boolean push(E value) {
        if (isFull()) {
            return false;
        }
        array[top++] = value;
        return true;
    }

    @Override
    public E pop() {
        if (isEmpty()) {
            return null;
        }
        return array[--top];
    }

    @Override
    public E peek() {
        if (isEmpty()) {
            return null;
        }
        return array[top-1];
    }

    @Override
    public boolean isEmpty() {
        return top == 0;
    }

    @Override
    public boolean isFull() {
        return top == array.length;
    }

    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            int p = top;
            @Override
            public boolean hasNext() {
                return p > 0;
            }

            @Override
            public E next() {
                return array[--p];
            }
        };
    }
}

题目训练

1、有效的括号

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

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。

  2. 左括号必须以正确的顺序闭合。

  3. 每个右括号都有一个对应的相同类型的左括号。

提示:

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

示例 1:

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

示例 2:

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

示例 3:

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

思路:

  • 遇到左括号, 把要配对的右括号放入栈顶
  • 遇到右括号, 若此时栈为空, 返回 false,否则把它与栈顶元素对比
    • 若相等, 栈顶元素弹出, 继续对比下一组
    • 若不等, 无效括号直接返回 false
  • 循环结束
    • 若栈为空, 表示所有括号都配上对, 返回 true
    • 若栈不为空, 表示右没配对的括号, 应返回 false

该方法使用了 ArrayStack 类

/**
* 遇到左括号,把要配对的右括号放入栈顶
* 遇到右括号,把它与栈顶元素对比
* 若相等,栈顶元素弹出,续对比 下一组
* 若不等,无效括号直接返回 false
* @param args
*/
// 测试用例
public static void main(String[] args) {
    E01Leetcode20 s = new E01Leetcode20();
    System.out.println(s.isValid("([{}])"));
    System.out.println(s.isValid("()[]{}"));
    System.out.println(s.isValid("()"));
    System.out.println("-----------------------");

    System.out.println(s.isValid("[)"));
    System.out.println(s.isValid("([)]"));
    System.out.println(s.isValid("([]"));
    System.out.println(s.isValid("("));

    System.out.println("-----------------------");
    System.out.println(s.isValid(")("));
    System.out.println(s.isValid("]"));
}

// 核心代码
public boolean isValid(String s) {
    ArrayStack<Character> stack = new ArrayStack<>(s.length());
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (c == '(') {
            stack.push(')');
        } else if (c == ']') {
            stack.push(']');
        } else if (c == '{') {
            stack.push('}');
        } else {
            if (!stack.isEmpty() && c == stack.peek()) {
                stack.pop();
            } else {
                return false;
            }
        }
    }
    return stack.isEmpty();
}

方法二:

class Solution {
    // 栈
    // 根据题目分为三种情况:
    // ① 已经遍历了所有字符串,但栈不为空,说明有相应的左括号没有右括号来匹配。
    // ② 遍历字符串过程中,发现栈里没有要匹配对应的字符串
    // ③ 遍历字符串过程中,栈已经空了,没有对应的字符。说明右括号没有对应的左括号

    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) {
                // .isEmpty  判断栈是否为空 || .peek() 返回栈顶元素
                return false;
            } else { // 如果是左括号判断是否和栈顶元素匹配
                deque.pop();
            }
        }
        // 最后判断栈中的元素是否匹配
        return deque.isEmpty();
    }
}

2、逆波兰表达式求值

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

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

注意:

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

提示:

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

示例 1:

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

示例 2:

输入: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

解题代码:

方法一:

/**
* - 遇到数字压入栈
* - 遇到运算符,就从栈弹出两个数字做运算,将结果压入栈
* - 遍历结束,栈中剩下的数字就是结果
* @param tokens
* @return
*/
// 核心代码
public int evalRPN(String[] tokens) {
    LinkedList<Integer> stack = new LinkedList<>();
    for (String t: tokens) {
        switch (t) {
            case "+":{
                Integer b = stack.pop();
                Integer a = stack.pop();
                stack.push(a + b);
                break;
            }
            case "-":{
                Integer b = stack.pop();
                Integer a = stack.pop();
                stack.push(a - b);
                break;
            }
            case "*":
                Integer b = stack.pop();
                Integer a = stack.pop();
                stack.push(a * b);
                break;
            case "/":
                b = stack.pop();
                a = stack.pop();
                stack.push(a / b);
                break;
            default:
                stack.push(Integer.parseInt(t));
        }
    }
    return stack.pop();
}

// 测试用例
public static void main(String[] args) {
    String[] tokens = {"10","6","9","3","+","-11","*","/","*","17","+","5","+"};
    System.out.println(new E02Leetcode150().evalRPN(tokens));
    tokens = new String[]{"2","1","+","3","*"};
    System.out.println(new E02Leetcode150().evalRPN(tokens));
}

方法二:

class Solution {
    // 逆波兰表达式:是一种 后缀 表达式,所谓后缀就是指运算符写在后面。
    // 平常使用的算式则是一种 中缀 表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
    // 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
    public int evalRPN(String[] tokens) {
        // 逆波兰表达式相当于二叉树中的后序遍历。
        // 定义栈
        Deque<Integer> deque = new LinkedList();
        
        // 遍历字符串
        for (String s : tokens) {
            if (s.equals("+")) {
                // 将二者之和压入栈中
                deque.push(deque.pop() + deque.pop());
            } else if (s.equals("-")) {
                // 将二者之差压入栈中
                // 因为栈是先进后出,所以应该是后出 - 前出。
                deque.push(-deque.pop() + deque.pop());
            } else if (s.equals("*")) {
                // 将二者之积压入栈内
                deque.push(deque.pop() * deque.pop());
            } else if (s.equals("/")) {
                // 将二者之商压入栈内
                int temp1 = deque.pop();
                int temp2 = deque.pop();
                deque.push(temp2 / temp1);
            } else {
                //
                deque.push(Integer.valueOf(s));
            }
        }
        return deque.pop();
    }
}

3、中缀表达式转后缀

/*
     思路
     1. 遇到数字, 拼串
     2. 遇到 + - * /
       - 优先级高于栈顶运算符 入栈
       - 否则将栈中高级或平级运算符出栈拼串, 本运算符入栈
     3. 遍历完成, 栈中剩余运算符出栈拼串
       - 先出栈,意味着优先运算
     4. 带 ()
       - 左括号直接入栈
       - 右括号要将栈中直至左括号为止的运算符出栈拼串

    |   |
    |   |
    |   |
    _____
     
    a+b
    a+b-c
    a+b*c
    a*b+c
    (a+b)*c
*/
public static void main(String[] args) {
    System.out.println(infixToSuffix("a+b"));
    System.out.println(infixToSuffix("a+b-c"));
    System.out.println(infixToSuffix("a+b*c"));
    System.out.println(infixToSuffix("a*b-c"));
    System.out.println(infixToSuffix("(a+b)*c"));
    System.out.println(infixToSuffix("a+b*c+(d*e+f)*g"));
}

static String infixToSuffix(String exp) {
    LinkedList<Character> stack = new LinkedList<>();
    StringBuilder sb = new StringBuilder(exp.length());
    for (int i = 0; i < exp.length(); i++) {
        char c = exp.charAt(i);
        switch (c) {
            case '+', '-', '*', '/' -> {
                if (stack.isEmpty()) {
                    stack.push(c);
                } else {
                    if (priority(c) > priority(stack.peek())) {
                        stack.push(c);
                    } else {
                        while (!stack.isEmpty() 
                               && priority(stack.peek()) >= priority(c)) {
                            sb.append(stack.pop());
                        }
                        stack.push(c);
                    }
                }
            }
            case '(' -> {
                stack.push(c);
            }
            case ')' -> {
                while (!stack.isEmpty() && stack.peek() != '(') {
                    sb.append(stack.pop());
                }
                stack.pop();
            }
            default -> {
                sb.append(c);
            }
        }
    }
    while (!stack.isEmpty()) {
        sb.append(stack.pop());
    }
    return sb.toString();
}

static int priority(char c) {
    return switch (c) {
        case '(' -> 0;
        case '*', '/' -> 2;
        case '+', '-' -> 1;
        default -> throw new IllegalArgumentException("不合法字符:" + c);
    };
}

4、双栈模拟队列

题目:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 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(双端队列)来模拟一个栈,只要是标准的栈操作即可。

提示:

  • 1 <= x <= 9
  • 最多调用 100pushpoppeekempty
  • 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)

示例 1:

输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]

解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

解题代码:

class MyQueue {

    Stack<Integer> StackIn;
    Stack<Integer> StackOut;

    /** 在这里初始化数据结构 */
    public MyQueue() {
        StackIn = new Stack<>(); // 负责进栈
        StackOut = new Stack<>(); // 负责出栈
    }
    
    /** 将元素 x 放入队列的尾部 */
    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() {
        // StackIn/StackOut  输入输出元素
        return StackIn.isEmpty() && StackOut.isEmpty();
    }

    // 如果 stackOut 为空,那么将 stackIn 中的元素全部放到 stackOut 中
    private void dumpstackIn() {
        if (!StackOut.isEmpty()) return;
        while (!StackIn.isEmpty()) {
            StackOut.push(StackIn.pop());
        }
    }
}

5、单队列模拟栈

题目:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

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

注意:

  • 你只能使用队列的基本操作 —— 也就是 push to backpeek/pop from frontsizeis empty 这些操作。

  • 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

提示:

  • 1 <= x <= 9
  • 最多调用100 次 push、pop、top 和 empty
  • 每次调用 pop 和 top 都保证栈不为空

示例:

输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]

解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

解题代码:

class MyStack {
    // Deque 接口继承了 Queue 接口
    // 所有 Queue 中的 add、poll、peek等效于 Deque 中的 addLast、pollFirst、peekFirst
    Deque<Integer> quel;

    /** 初始化数据 */
    public MyStack() {
        quel = new ArrayDeque<>();
    }
    
    /** 将元素 x 压入栈顶 */
    public void push(int x) {
        quel.addLast(x);
    }
    
    /** 移除并返回栈顶元素 */
    public int pop() {
        int size = quel.size();
        size--;
        // 将 que1 导入 que2 ,但留下最后一个值
        while (size-- > 0) {
            quel.addLast(quel.peekFirst());
            quel.pollFirst();
        }

        int res = quel.pollFirst();
        return res;
    }
    
    /** 返回栈顶元素 */
    public int top() {
        return quel.peekLast();
    }
    
    /** 判断栈顶是否为空,为空返回true */
    public boolean empty() {
        return quel.isEmpty();
    }
}

最后

对各位小伙伴有帮助的话,希望可以点赞❤️+收藏⭐,谢谢各位大佬~~🙌🙌🙌

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乌云暮年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值