栈和队列(超详细Java实现)

一.栈(Stack)

1.概念

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

压栈(push):栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
在这里插入图片描述

出栈(pop):栈的删除操作叫做出栈。出数据在栈顶。
在这里插入图片描述

可以看到,先入栈的元素要等后入栈的元素出栈后才能出栈,栈中的元素总是遵循后进先出LIFO(Last In First Out)的原则。

生活中的例子:

  1. 堆积的碗

在这里插入图片描述

  1. 羽毛球摆放

在这里插入图片描述

注意: 将此处的栈与JVM内存模型中的栈区分开

此处的栈是一种数据结构
JVM栈特指JVM中一段内存区域

JVM : Java虚拟机

2.栈的使用

  1. Java标准库中提供的栈
    在这里插入图片描述
    可以看到,Stack继承了Vector,Vector是动态的顺序表,与ArrayList类似,不同的是,Vector是线程安全的.

  2. 实现的方法
    在这里插入图片描述

Stack()构造一个空的栈
E push(E e)将e入栈,并返回e
E pop()将栈顶元素出栈并返回
E peek()获取栈顶元素
int size()获取栈中有效元素个数 (继承自Vector)
boolean empty()检测栈是否为空
public static void main(String[] args){
        Stack<Integer> stack = new Stack<>();

        //入栈
        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);

        System.out.println("栈中有效元素个数 : "+ stack.size()); // 输出 4

        System.out.println("获取栈顶元素 : "+stack.peek()); // 获取栈顶元素,但是不出栈,栈中元素不变

        stack.pop();   // 出栈  元素 4 出栈 ,栈中剩余元素 3,2,1

        System.out.println("获取栈顶元素 : " + stack.pop()); // 获取栈顶元素,出栈, 此时栈中剩余 2,1两个元素

        System.out.println("栈中有效元素个数 : "+ stack.size()); // 输出 2

        System.out.println("stack是否为空 : "+ stack.isEmpty()); // 判断栈中是否为空

    }

输出结果:
在这里插入图片描述

3.模拟实现一个栈

栈是一个特殊的顺序表,所以采用链表和数组的方式都可实现,但是,一般采用数组的方式实现.

1. 构造方法
class MyStack{

    private int[] arr;

    // size 记录栈中元素个数
    private int size;

    public MyStack(){
        // 调用无参构造方法 默认最大容量12
        this(12);
    }

    public MyStack(int MaxSize){
        this.arr = new int[MaxSize];
    }
}
2. 入栈(push)

入栈时判断栈是否已满,如果栈满,需要对数组扩容

// 入栈
    public int push(int value){
        if(this.size == arr.length){
            // 栈满 ,需要扩容

            int[] copyArr;
            // 复制arr 数组并扩容一倍
            copyArr = Arrays.copyOf(arr,2 * arr.length);
            arr = copyArr;

        }
        //将元素添加到size位置
        this.arr[size] = value;
        // 元素个数加一
        this.size++;
        // 返回添加元素
        return value;
    }
3. 出栈(pop)

出栈时,判断栈中元素是否为空,如果为空,抛出异常

// 出栈
    public int pop(){
        if(this.size == 0){
            //没有元素
            //抛出运行时异常,此处也可以自定义异常
            throw new RuntimeException("栈中没有元素,不能出栈....");
        }
        // 获得栈顶元素
        int value = this.arr[size - 1];
        // size - 1 之后, 下一次插入时会覆盖原数据,利用覆盖替代删除
        this.size--;
        return value;
    }
4.获取栈顶元素(peek)
// 获取栈顶元素
    public int peek(){
        if(this.size == 0){
            //没有元素
            //抛出运行时异常,此处也可以自定义异常
            throw new RuntimeException("栈中没有元素,不能出栈....");
        }
        return this.arr[this.size - 1];
    }
5.获取元素个数(getSize)
//获取元素个数
    public int getSize(){
        return this.size;
    }
6.判断栈是否为空(isEmpty)
//判断元素是否为空
    public boolean isEmpty(){
        return this.size == 0;
    }
7.完整代码
import java.util.Arrays;

public class MyStack{

    private int[] arr;

    // size 记录栈中元素个数
    private int size;

    public MyStack(){
        // 调用无参构造方法 默认最大容量12
        this(12);
    }

    public MyStack(int MaxSize){
        this.arr = new int[MaxSize];
    }

    // 入栈
    public int push(int value){
        if(this.size == arr.length){
            // 栈满 ,需要扩容

            int[] copyArr;
            // 复制arr 数组并扩容一倍
            copyArr = Arrays.copyOf(arr,2 * arr.length);
            arr = copyArr;

        }
        //将元素添加到size位置
        this.arr[size] = value;
        // 元素个数加一
        this.size++;
        // 返回添加元素
        return value;
    }

    // 出栈
    public int pop(){
        if(isEmpty()){
            //没有元素
            //抛出运行时异常,此处也可以自定义异常
            throw new RuntimeException("栈中没有元素,不能出栈....");
        }
        // 获得栈顶元素
        int value = this.arr[size - 1];
        // size - 1 之后, 下一次插入时会覆盖原数据,利用覆盖替代删除
        this.size--;
        return value;
    }

    // 获取栈顶元素
    public int peek(){
        if(isEmpty()){
            //没有元素
            //抛出运行时异常,此处也可以自定义异常
            throw new RuntimeException("栈中没有元素,不能出栈....");
        }
        return this.arr[this.size - 1];
    }

    //获取元素个数
    public int getSize(){
        return this.size;
    }

    //判断元素是否为空
    public boolean isEmpty(){
        return this.size == 0;
    }
}
8.泛型实现
import java.util.Arrays;

public class MyStack<T>{

    private T[] arr;

    // size 记录栈中元素个数
    private int size;

    public MyStack(){
        // 调用无参构造方法 默认最大容量12
        this(12);
    }

    public MyStack(int MaxSize){
        this.arr = (T[]) new Object[MaxSize];
    }

    // 入栈
    public T push(T value){
        if(this.size == arr.length){
            // 栈满 ,需要扩容

            T[] copyArr;
            // 复制arr 数组并扩容一倍
            copyArr = Arrays.copyOf(arr,2 * arr.length);
            arr = copyArr;

        }
        //将元素添加到size位置
        this.arr[size] = value;
        // 元素个数加一
        this.size++;
        // 返回添加元素
        return value;
    }

    // 出栈
    public T pop(){
        if(isEmpty()){
            //没有元素
            //抛出运行时异常,此处也可以自定义异常
            throw new RuntimeException("栈中没有元素,不能出栈....");
        }
        // 获得栈顶元素
        T value = this.arr[size - 1];
        // size - 1 之后, 下一次插入时会覆盖原数据,利用覆盖替代删除
        this.size--;
        return value;
    }

    // 获取栈顶元素
    public T peek(){
        if(isEmpty()){
            //没有元素
            //抛出运行时异常,此处也可以自定义异常
            throw new RuntimeException("栈中没有元素,不能出栈....");
        }
        return this.arr[this.size - 1];
    }

    //获取元素个数
    public int getSize(){
        return this.size;
    }

    //判断元素是否为空
    public boolean isEmpty(){
        return this.size == 0;
    }
}

二.队列(Queue)

1.概念

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)

入队列:进行插入操作的一端称为队尾(Tail/Rear)
出队列:进行删除操作的一端称为队头(Head/Front)
在这里插入图片描述
队列是只允许在一端插入,一端删除的数据结构.

生活中的例子 :

  1. 排队

在这里插入图片描述

2.队列的使用

  1. java标准库中的队列

在这里插入图片描述
可以看出,Java中的Queue是一个接口,需要通过实现这个接口的类来实例化对象,LinkedList实现了Queue接口,可以通过LinkedList实例化,如Queue<Integer> queue = new LinkedList<>();

  1. 实现的方法

在这里插入图片描述
注意 : Queue中,插入和删除操作都有2个方法可以实现,当我们使用队列时,通常采用offer(E)添加元素和poll删除元素,而不使用addremove方法.

方法功能
boolean offer(E e)入队列
E poll()出队列
peek()获取队头元素
int size()获取队列中有效元素个数
boolean isEmpty()检测队列是否为空
public static void main(String[] args) {
        Queue<Integer> q = new LinkedList<>();
        //插入元素
        q.offer(1);
        q.offer(2);
        q.offer(3);
        q.offer(4);
        q.offer(5);

        System.out.println("元素个数 : "+q.size()); // 获取元素个数 输出5

        System.out.println("获取队头元素 : "+q.peek()); // 获取队头元素,但不删除元素
        q.poll();
        System.out.println("出队列元素 : "+q.poll()); // 从队头出队列,并将删除的元素返回

        if(q.isEmpty()){
            System.out.println("队列空");
        }else{
            System.out.println("元素个数 : "+q.size());
        }
    }

输出结果 :

在这里插入图片描述

3.模拟实现一个队列

要想实现一个队列,也可以采用链表和数组两种存储数据的方式,那么,到底应该用那种方式实现
对于数组来说,入队列和出队列操作都相对简单,但是可能会造成空间大量浪费,如:

![在这里插入图片描述](https://img-blog.csdnimg.cn/4b786d9ea53345b0b77b63116023f0a9.png
当head在3下标时,下标 < 3 的位置无法在利用,上面的例子中,数组的大小为9,而最后队列中只有6个元素就已经无法插入新的元素,导致浪费大量的空间,而链表因为存储空间不连续,很好的避免了这一问题,出队列时就可以释放资源,解决了内存利用率低的问题.
在这里插入图片描述

所以先采用链表的方式,构造一个队列.

1.构造方法
public class MyLinkedListQueue {

    // 结点
    static class Node {
        public int val;
        public Node next;

        public Node(int val) {
            this.val = val;
        }
    }

    public Node head;
    public Node last;
    public int useSize;
	
	//全部初始化为空
	public MyLinkedListQueue(){
        head = null;
        last = null;
    }

}
2.入队列(offer)

由于是链表,所以入队列时不需要考虑扩容问题,代码执行时,动态分配空间.

	//入队列
	public void offer(int val) {
		//构造一个结点
        Node tmp = new Node(val);
        //当head为空时,说明队列中没有元素,直接让新结点成为头尾结点
        if(head == null){
            head = tmp;
            last = tmp;
        }else{
            last.next = tmp;
            last = tmp
        }
        //元素个数加1
        useSize++;
    }
3.出队列(poll)

出队列时需要判断队列是否为空

 public int poll() {
 		//如果队列为空,抛出异常
        if(isEmpty()) {
            throw new RuntimeException("队列为空");
        }
        int val = head.val;
        // 让队头指向下一个结点
        head = head.next;
        //如果head 为空了,说明开始只有一个元素,把last也置空
        if(head == null){
            last = null;
        }
        useSize--;
        return val;
    }
4.获取队头元素(peek)
	public int peek() {
        if(isEmpty()){
            throw new RuntimeException("链表为空");
        }
        return head.val;
    }
5.获取元素个数(getSize)
	public int size() {
        return useSize;
    }
6.判断队列是否为空(isEmpty)
	public boolean isEmpty(){
        return useSize==0;
    }
7.完整代码
	public class MyLinkedListQueue {
    // 结点
    static class Node {
        public int val;
        public Node next;

        public Node(int val) {
            this.val = val;
        }
    }

    public Node head;
    public Node last;
    public int useSize;

    public MyLinkedListQueue(){
        head = null;
        last = null;
    }

    public void offer(int val) {
        Node tmp = new Node(val);
        if(head == null){
            head = tmp;
            last = tmp;
        }else{
            last.next = tmp;
            last = tmp;
        }
        useSize++;
    }

    public int poll() {
        if(isEmpty()) {
            throw new RuntimeException("队列为空");
        }
        int val = head.val;
        head = head.next;
        if(head == null){
            last = null;
        }
        useSize--;
        return 0;
    }

    public int peek() {
        if(isEmpty()){
            throw new RuntimeException("链表为空");
        }
        return head.val;
    }

    public int size() {
        return useSize;
    }
    public boolean isEmpty(){
        return useSize==0;
    }
}
8.泛型实现
public class MyLinkedListQueue<T> {

    // 结点
    static class Node<T> {
        public T val;
        public Node<T> next;

        public Node(T val) {
            this.val = val;
        }
    }

    public Node<T> head;
    public Node<T> last;
    public int useSize;

    public MyLinkedListQueue(){
        head = null;
        last = null;
    }

    public void offer(T val) {
        Node<T> tmp = new Node<>(val);
        if(head == null){
            head = tmp;
            last = tmp;
        }else{
            tmp.next = head;
            head = tmp;
        }
        useSize++;
    }

    public T poll() {
        if(isEmpty()) {
            throw new RuntimeException("队列为空");
        }
        T val = head.val;
        head = head.next;
        if(head == null){
            last = null;
        }
        useSize--;
        return val;
    }

    public T peek() {
        if(isEmpty()){
            throw new RuntimeException("链表为空");
        }
        return head.val;
    }

    public int size() {
        return useSize;
    }
    public boolean isEmpty(){
        return useSize==0;
    }
}

4. 实现一个循环队列

实际中我们有时还会使用一种队列叫循环队列,循环队列通常使用数组实现。

利用数组实现一个队列可能会浪费大量的空间,那么,就可以使用循环队列,也能解决资源浪费的问题.
在这里插入图片描述
如图所示循环队列,其本质也是一个数组
如图所示 :
在这里插入图片描述
当数组小下标有空闲位置时,head一旦==数组长度就会循环从0开始,如上图,4,5,63个位置为空闲位置

1. 入队列
//入队列
    public void offer(int val){
        if(size == elem.length){
            //可扩容,此处不实现
            throw new RuntimeException("队列已满...");
        }
        elem[rear] = val;
        //如果rear到达数组长度,则置0
        if(rear + 1 >= elem.length){
            rear = 0;
        }else {
            rear++;
        }
        size++;
    }
2. 出队列
	public int poll(){
        if(size == 0){
            throw new RuntimeException("队列为空...");
        }
        int val = elem[front];
        if(front + 1 >= elem.length){
            front = 0;
        }else {
            front++;
        }
        size--;
        return val;
    }
3. 获取队首元素
 //获取队首元素
    public int peek(){
        if(size == 0){
            throw new RuntimeException("队列为空...");
        }
        return elem[front];
    }
4. 获取队列中元素个数
//判断元素个数
    public int size(){
        return size;
    }
5.判断队列是否为空
// 判断是否为空
    public boolean isEmpty(){
        return size == 0;
    }
6. 完整代码
public class MyCircularQueue {

    private final int[] elem;
    private int front;      //队头下标
    private int rear;       //队尾下标
    private int size;

    public MyCircularQueue() {
        elem = new int[24];
    }

    //入队列
    public void offer(int val){
        if(size == elem.length){
            //可扩容,此处不实现
            throw new RuntimeException("队列已满...");
        }
        elem[rear] = val;
        //如果rear到达数组长度,则置0
        if(rear + 1 >= elem.length){
            rear = 0;
        }else {
            rear++;
        }
        size++;
    }

    public int poll(){
        if(size == 0){
            throw new RuntimeException("队列为空...");
        }
        int val = elem[front];
        if(front + 1 >= elem.length){
            front = 0;
        }else {
            front++;
        }
        size--;
        return val;
    }

    //判断元素个数
    public int size(){
        return size;
    }

    //获取队首元素
    public int peek(){
        if(size == 0){
            throw new RuntimeException("队列为空...");
        }
        return elem[front];
    }

    // 判断是否为空
    public boolean isEmpty(){
        return size == 0;
    }
}

三.相关题目

1. 有效的括号

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

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

样例1:

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

样例2:

输入:s = "([)]"
输出:false
  • 如果传入字符串长度为奇数,直接返回false
  • 如果第一个元素为右括号,则直接false
  • 当连续两个元素为不同类型的左右括号时,无法再正确完成匹配,返回false ,如"((]"
  • 遍历完成字符串时,还需判断栈中元素是否为空,若不为空,返回false

代码 :

public boolean isValid(String s) {
        if(s.length() % 2 != 0){
            return false;
        }
        // 创建一个栈,
        Stack<Character> stack = new Stack<>();

        //循环遍历每一个字符
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            //如果下一个字符为反括号,进入判断
            if((ch == ')' || ch == ']' || ch == '}')){
                //如果 此时栈中没有元素,反括号在第一个位置,不可能再匹配,返回false
                if(stack.isEmpty()){
                    return false;
                }
                char c = stack.peek();
                //如果前一个元素为对应左括号,则出栈
                if(c == '(' && ch == ')' || c == '[' && ch == ']' || c == '{' && ch == '}') {
                    stack.pop();
                }else {
                    // 否则会出现两种括号交叉出现,无法正确匹配,返回false
                    return false;
                }
            //如果下一个字符为左括号,入栈
            }else {
                stack.push(ch);
            }
        }
        //遍历完成之后判断栈中元素是否为空,如果为空,说明有多余的括号没有匹配完,返回false
        return stack.isEmpty();
    }
2. 逆波兰表达式求值

根据 逆波兰表示法,求表达式的值。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
注意 两个整数之间的除法只保留整数部分。
可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

样例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
  • 逆波兰表达式遵循从左到右的运算,所以采用栈来计算
  • 如果遇到数字,将数字入栈
  • 如果遇到运算符,将栈内出栈2个元素,通过运算符计算后,将计算结果放入栈中.
public int evalRPN(String[] tokens) {
        // 创建一个栈
        Stack<Integer> stack = new Stack<>();
        // 遍历每一个字符串
        for (String token : tokens) {
            //判断是否为数字,如果是,加入栈中,如果不是,取出2个元素计算后重新加入栈中
            if (token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/")) {
                int pop1 = stack.pop();
                int pop2 = stack.pop();
                switch (token) {
                    case "+":
                        stack.push(pop2 + pop1);
                        break;
                    case "-":
                        stack.push(pop2 - pop1);
                        break;
                    case "*":
                        stack.push(pop2 * pop1);
                        break;
                    case "/":
                        stack.push(pop2 / pop1);
                        break;
                }
            } else {
                stack.push(Integer.parseInt(token));
            }
        }
        //计算完毕后,剩下一个元素,即为结果
        return stack.pop();
    }
3. 出栈入栈次序匹配

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列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]的弹出顺序,
要求435必须在12前压入,且12不能弹出,但是这样压入的顺序,
1又不能在2之前弹出,所以无法形成的,返回false  
  • 创建一个辅助栈,和i,j两下标分别记录入栈与出栈序列
  • 遍历 入栈数组
  • 如果 入栈元素与出栈元素不相等,将入栈元素加入栈中
  • 如果 入栈元素与出栈元素相等,出栈下标往后移
  • 通过循环,判断栈顶元素与出栈数组是否相等,如果相等,辅助栈出栈,出栈下标加一,如果辅助栈类为空,结束循环
  • 如果全部出栈,出栈下标因该等于出栈数组长度
	public boolean IsPopOrder(int[] pushA, int[] popA) {
        Stack<Integer> stack = new Stack<>();
        //记录popA下标
        int j = 0;
        //遍历 入栈数组
        for (int i = 0; i < pushA.length; i++) {
            //如果 入栈元素与出栈元素不相等,将入栈元素加入栈中
            if (pushA[i] != popA[j]) {
                stack.push(pushA[i]);
            //如果 入栈元素与出栈元素相等,出栈下标加一    
            } else {
                j++;
                //通过循环,判断栈顶元素与出栈数组是否相等,如果相等,辅助栈出栈,出栈下标加一
                //如果辅助栈类为空,结束循环
                while (!stack.isEmpty() && stack.peek() == popA[j]){
                    stack.pop();
                    j++;
                }
            }
        }
        // 如果全部出栈,出栈下标因该等于出栈数组长度
        return j == popA.length;
    }
4. 实现一个最小栈

设计一个支持 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.

来源:力扣(LeetCode
  • 当一个元素要入栈时,取当前最小栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入最小栈中
  • 出栈时,把最小栈的栈顶元素也一并弹出
  • 所以在任意一个时刻,栈内元素的最小值就存储在最小栈的栈顶元素中
 class MinStack {
        Stack<Integer> stack;
        //维护一个最小栈
        Stack<Integer> minStack;

        public MinStack() {
            stack = new Stack<>();
            minStack = new Stack<>();
            minStack.push(Integer.MAX_VALUE);
        }

        public void push(int x) {
            stack.push(x);
            //将最小值加入最小栈中
            minStack.push(Math.min(minStack.peek(), x));
        }

        public void pop() {
            stack.pop();
            minStack.pop();
        }

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

        public int getMin() {
            return minStack.peek();
        }
    }

来源:力扣(LeetCode)

在这里插入图片描述

博主水平有限,欢迎指正

  • 76
    点赞
  • 250
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 24
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

努力写代码的菜鸟

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

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

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

打赏作者

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

抵扣说明:

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

余额充值