数据结构 --- 树

1、二叉树

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

  1. 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
  2. 二叉树的子树有左右之分,其子树的次序不能颠倒。

在这里插入图片描述

 

2、 二叉树的遍历 --- 深度优先

深度优先可以分为以下三种

 1、前序遍历

规则:

  1. 先访问该节点
  2. 在访问左子树
  3. 在访问右子树

上述遍历顺序:1、2、4、3、5、6

    public void preOrder(TreeNode root) {
        // 递归结束条件
        if (root == null) {
            return;
        }
        // 当前节点
        System.out.println(root);

        // 左子树
        TreeNode left = root.left;
        preOrder(left);

        // 右子树
        TreeNode right = root.right;
        preOrder(right);
    }
    public void preOrder1(TreeNode root) {
        TreeNode curr = root;
        Stack<TreeNode> stack = new Stack<>();

        while (curr != null || stack.isEmpty()) {
            if (curr != null) {
                // 处理逻辑,处理当前节点
                System.out.println(curr); 
                // 利用栈,记录来的时候的顺序
                stack.push(curr);
                // 遍历左子树
                curr = curr.left;
            } else {
                // 从栈中弹出元素,遍历右子树
                TreeNode pop = stack.pop();
                curr = pop.right;
            }
        }
    }

2、中序遍历

 规则:

  1. 先访问左子树
  2. 在访问该节点
  3. 在访问右子树

上述遍历顺序:4、2、1、5、3、6

    public void inOrder(TreeNode root) {
        // 递归结束条件
        if (root == null) {
            return;
        }

        // 左子树
        TreeNode left = root.left;
        inOrder(left);

        // 当前节点
        System.out.println(root);

        // 右子树
        TreeNode right = root.right;
        inOrder(right);
    }
    public void inOrder1(TreeNode root) {
        TreeNode curr = root;
        Stack<TreeNode> stack = new Stack<>();

        while (curr != null || stack.isEmpty()) {
            if (curr != null) {
                // 利用栈,记录来的时候的顺序
                stack.push(curr);
                // 遍历左子树
                curr = curr.left;
            } else {
                // 处理逻辑,处理当前节点
                System.out.println(curr);
                // 从栈中弹出元素,遍历右子树
                TreeNode pop = stack.pop();
                curr = pop.right;
            }
        }
    }

3、后续遍历

 规则:

  1. 先访问左子树
  2. 在访问右子树
  3. 在访问该节点

上述遍历顺序:4、2、5、6、3、1

    public void postOrder(TreeNode root) {
        // 递归结束条件
        if (root == null) {
            return;
        }

        // 左子树
        TreeNode left = root.left;
        postOrder(left);

        // 右子树
        TreeNode right = root.right;
        postOrder(right);

        // 当前节点
        System.out.println(root);
    }
    public void postOrder1(TreeNode root) {
        TreeNode curr = root;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode pop = null; // 记录最近一次弹出栈的元素
        while (curr != null || stack.isEmpty()) {
            if (curr != null) {
                // 利用栈,记录来的时候的顺序
                stack.push(curr);
                // 遍历左子树
                curr = curr.left;
            } else {
                // 从栈中弹出元素,遍历右子树
                TreeNode peek = stack.peek();
                // 判断栈顶元素的右子树是否处理,如果已经处理,就可以弹出栈
                if (peek.right == null || peek.right == pop) {
                    pop = stack.pop();
                    // 处理逻辑,处理当前节点
                    System.out.println(pop.val);
                } else {
                    curr = peek.right;
                }
            }
        }
    }

例题:

101. 对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

解题思路

  1. 轴对称,其实就是左右两边子树是否对称
  2. 从根节点开始,比较左子节点和右子节点是否相等
  3. 比较左子节点的左子节点和右子节点的右子节点是否相等
  4. 比较左子节点的左右子节点和右子节点的右左子节点是否相等

有点绕,需要慢慢体会。

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if (root == null) {
            return true;
        }
        return check(root.left, root.right);

    }

    public boolean check(TreeNode left, TreeNode right) {
        if (left == null && right == null) {
            return true;
        }
        if (left == null || right == null) {
            return false;
        }
        if (left.val != right.val) {
            return false;
        }
        return check(left.left, right.right) && check(left.right, right.left);
    }
}

104. 二叉树的最大深度

给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

解题思路:

递归:

找到左子树的深度和右子树的深度较大的哪一个。然后加一。

class Solution {
    public int maxDepth(TreeNode root) {
        if(root==null){
            return 0;
        }else {
            int leftHeight=maxDepth(root.left);
            int rightHeight=maxDepth(root.right);
            return Math.max(leftHeight,rightHeight)+1;
        }
    }
}

BFS层序遍历

层序遍历,知道多少层,就知道最大深度了。

public class Test104 {
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.offer(root);
        int level = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode poll = queue.poll();
                if (poll.left != null) {
                    queue.offer(poll.left);
                }
                if (poll.right != null) {
                    queue.offer(poll.right);
                }
            }
            level++;
        }
        return level;
    }
}

111. 二叉树的最小深度

给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

解题1:

递归,但是要注意一点,如果左右子树为null的时候,就不应该计算进去。

    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int left = minDepth(root.left);
        int right = minDepth(root.right);
        if (left == 0) {
            return right + 1;
        }
        if (right == 0) {
            return left + 1;
        }
        return Math.min(left, right) + 1;
    }

解题2:

层序遍历,这种效率是较高的

    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.offer(root);
        int level = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode poll = queue.poll();
                if (poll.left != null) {
                    queue.offer(poll.left);
                }
                if (poll.right != null) {
                    queue.offer(poll.right);
                }
                // 这个就是叶子节点,只要遇到第一个叶子节点,就会返回值
                if (poll.left == null && poll.right == null) {
                    return ++level;
                }
            }
            level++;
        }
        return level;
    }

226. 翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

解题思路:

很简单的一道递归调用,将节点的左右节点互相调换即可。

class Solution {
    public TreeNode invertTree(TreeNode root) {
        dsf(root);
        return root;
    }

    public void dsf(TreeNode node) {
        if (node == null) {
            return;
        }
        TreeNode tmp = node.left;
        node.left = node.right;
        node.right = tmp;
        dsf(node.right);
        dsf(node.left);
    }
}

根据后缀表达式,构建一颗树。

 

    // 根据后缀表达式生成树
    public TreeNode constructExpressionTree(String[] token) {
        Stack<TreeNode> stack = new Stack<>();

        for (String s : token) {
            switch (s) {
                case "+", "-", "*", "/" -> {
                    TreeNode right = stack.pop();
                    TreeNode left = stack.pop();
                    TreeNode parent = new TreeNode(s);
                    parent.right = right;
                    parent.left = left;
                    stack.push(parent);
                }
                default -> {
                    stack.push(new TreeNode(s));
                }
            }
        }
        return stack.peek();
    }

105 、从前序与中序遍历序列构造二叉树

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

解题思路

preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]

  1. 前序遍历,第一个节点肯定是头节点。确定头结点的值,可以在中序遍历中定位到头节点位置
  2. 中序数组中,头节点左侧是左子树,右侧是右子树
  3. 根据中序数组中判断出来的左右子树,可以在前序数组中分割出左右子树。
  4. 这样,就得到了根节点,左子树的前序和中序数组,右子树的前序和中序数组。就找到了递归的条件
  5. 递归结束条件,如果一个节点没有左右子树,说明前序数组和中序数组长度为0,。
public class Test105 {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if (preorder.length == 0 || inorder.length == 0) {
            return null;
        }
        int rootValue = preorder[0];
        TreeNode root = new TreeNode(rootValue);

        for (int i = 0; i < inorder.length; i++) {
            // 找到中序数组中的根节点位置,左边是左子树,右边是右子树
            if (rootValue == inorder[i]) {
                // 中序数组,划分成左右两边
                int[] inLeft = Arrays.copyOfRange(inorder, 0, i);
                int[] inRight = Arrays.copyOfRange(inorder, i + 1, inorder.length);

                // 前序数组,也划分成左右两边
                int[] preLeft = Arrays.copyOfRange(preorder, 1, i + 1);
                int[] preRight = Arrays.copyOfRange(preorder, i + 1, inorder.length);

                //递归调用
                root.left = buildTree(preLeft, inLeft);
                root.right = buildTree(preRight, inRight);

                break;
            }
        }
        return root;
    }
}

106、从中序与后序遍历序列构造二叉树

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

解题思路,和105题一样

    public TreeNode buildTree1(int[] inorder, int[] postorder) {
        if (postorder.length == 0 || inorder.length == 0) {
            return null;
        }
        int length = postorder.length;
        int rootValue = postorder[length-1];
        TreeNode root = new TreeNode(rootValue);

        for (int i = 0; i < length; i++) {
            // 找到中序数组中的根节点位置,左边是左子树,右边是右子树
            if (rootValue == inorder[i]) {
                // 中序数组,划分成左右两边
                int[] inLeft = Arrays.copyOfRange(inorder, 0, i);
                int[] inRight = Arrays.copyOfRange(inorder, i + 1, inorder.length);

                // 后序数组,也划分成左右两边
                int[] postLeft = Arrays.copyOfRange(postorder, 0, i );
                int[] postRight = Arrays.copyOfRange(postorder, i , inorder.length-1);

                //递归调用
                root.left = buildTree(inLeft,postLeft);
                root.right = buildTree(inRight,postRight);

                break;
            }
        }
        return root;
    }

3、二叉搜索树

给树的节点定义一个key,是不可用重复的。

二叉搜索树满足:节点的key大于左子树的key,小于右子树的key。

二叉搜索树的实现:

 1、先定义二叉查找树的节点

二叉查找树节点
    static class BSTTreeNode {
        int key;
        Object value;
        BSTTreeNode left;
        BSTTreeNode right;
        public BSTTreeNode(int key) {
            this.key = key;
        }
    }

 2、定义二叉查找树的方法

二叉查找树方法
public class BSTTree {
    // 根节点
    BSTTreeNode root;

    // 根据key,获取value
    public Object get(int key) {
        return doGet(root, key);
    }

    // 获得树中最小key关联的值
    public Object min() {
        return null;
    }

    // 获得树中最大key关联的值
    public Object max() {
        return null;
    }

    // 找到一个key的前驱(就是比他key小的最大的值)
    public Object successor(int key) {
        return null;
    }

    // 找到一个key的后继(就是比他key大的最小的值)
    public Object predecessor(int key) {
        return null;
    }

    // 根据key删除节点
    public Object delete(int key) {
        return null;
    }

    public void put(int key, Object value) {
        
    }
}

3、get方法的实现

循环或者递归,比较key的值,key比当前节点的key大,就往右子树找,key比当前节点的key小,就往左子树找,找不到就返回null。

递归
    // 根据key,获取value
    public Object get(int key) {
        return doGet(root, key);
    }

    private Object doGet(BSTTreeNode node, int key) {
        if (node == null) {
            return null;
        }

        if (node.key < key) {
            return doGet(node.right, key);// 向右找
        } else if (node.key > key) {
            return doGet(node.left, key); // 向左找
        } else {
            return node.value; // 找到了
        }
    }

非递归
    public Object get1(int key) {
        BSTTreeNode node = root;
        while (node != null) {
            if (node.key > key) {
                node = node.left;
            } else if (node.key < key) {
                node = node.right;
            } else {
                return node;
            }
        }
        return null;
    }

4.min方法的实现:沿着根节点,一直往左找

    // 获得树中最小key关联的值
    public Object min() {
        return min(root);
    }

    public Object min(BSTTreeNode bstTreeNode) {
        BSTTreeNode node = bstTreeNode;
        while (true) {
            if (node == null) {
                return node;
            } else {
                node = node.left;
            }
        }
    }

5、max方法的实现:沿着根节点,一直往右找


    // 获得树中最大key关联的值
    public Object max() {
        return max(root);
    }

    public Object max(BSTTreeNode bstTreeNode) {
        BSTTreeNode node = bstTreeNode;
        while (true) {
            if (node == null) {
                return node;
            } else {
                node = node.right;
            }
        }
    }

6、增加节点方法实现

  1. 分为两种,key已经存在了,就更新value,key不存在,就新增节点到树里面。
  2. 判断key是否存在,也要用到查找逻辑,可以在get的基础上进行修改。
  3. 首先,如果树是空的,那么直接创建节点赋值给root
  4. 查找树中的key,比较大小,往左或者往右进行查找
  5. 如果查找到了,就直接修改value的值。
  6. 如果找不到,说明需要新增,新增在那个节点下面呢?因此要定义一个临时变量,记录4步骤中查到的最后一个不为null的节点。
  7. 得到6中的节点,判断key的值,选择插入这个节点的左边还是右边。
    // key存在,更新,key不存在,新增
    public void put(int key, Object value) {
        BSTTreeNode add = new BSTTreeNode(key, value);
        BSTTreeNode node = root;
        if (root == null) {
            root = add;
        }

        BSTTreeNode parent = null;
        while (node != null) {
            parent = node; // parent 记录循环中的最后一个节点
            if (node.key > key) {
                node = node.left;
            } else if (node.key < key) {
                node = node.right;
            } else {
                // 找到了,更新操作
                node.value = value;
                return;
            }
        }
        // 没有找到,新增
        if (parent.key > key) {
            parent.left = add;
        } else {
            parent.right = add;
        }
    }

7、删除一个节点

  1. 删除节点没有左孩子,将右孩子给parent
  2. 删除节点没有右孩子,将左孩子给parent
  3. 删除节点没有左右孩子,已经被1,2两个场景包括了
  4. 删除节点左右孩子都有:
    1. 首先找到被删除节点的后继节点(其实就是去右子树中一直往左找到的最后一个)
    2. 如果找到的后继节点与要删除的节点相邻,说明这个后继节点没有左节点,可以直接把这个后继节点顶上
    3. 如果不相邻,把这个后继节点的右孩子顶到后继节点的位置(后继节点不可能有左孩子),然后把后继节点取代被删除节点就可以。
    // 根据key删除节点
    public Object delete(int key) {
        BSTTreeNode node = root;
        BSTTreeNode parent = null;
        while (node != null) {
            if (key < node.key) {
                parent = node;
                node = node.left;
            } else if (key > node.key) {
                parent = node;
                node = node.right;
            } else {
                // node就是当前节点,就不需要给parent赋值了
                break;
            }
        }

        if (node == null) {
            // 没找到这个节点
            return null;
        }

        // 删除操作
        if (node.left == null) {
            // 没有左孩子,那么就把右孩子顶上去
            shift(parent, node, node.right);
        } else if (node.right == null) {
            // 没有右孩子,那么就把左孩子顶上去
            shift(parent, node, node.left);
        } else {
            // 左右孩子都有

            // 先找后继节点,后继节点肯定是去右子树中去寻找的
            BSTTreeNode preNode = node.right;
            BSTTreeNode preParent = node;
            // 后继节点肯定就是一直向左找的哪个节点。
            while (preNode.left != null) {
                preParent = preNode;
                preNode = preNode.left;
            }

            // 如果后继节点和待删除节点不相邻,说明preParent发生了变化
            if (preParent != node) {
                // 先处理后继节点的后事 
                // --- 其实就是把后继节点的右孩子,提升,因为后继节点不可能有左孩子
                shift(preParent, preNode, preNode.right);
                preNode.right = node.right;
            }

            // 后继节点取代被删除节点
            shift(parent, node, preNode);
            preNode.left = node.left;
        }
        return node.value;
    }

8、找到一个节点的先驱节点

  1. 如果一个节点有左子树,那么这个节点就是左子树中最大的哪个节点
  2. 如果一个节点没有左子树
    1. 这个节点就是他自左边来的最近的祖先节点 (3的是2,5的是4)
    2. 如果没有自左边来的最近的祖先节点,那么没有这个节点(1没有)

    // 找到一个key的前驱(就是比他key小的最大的值)
    public Object successor(int key) {
        BSTTreeNode node = root;
        BSTTreeNode ancestorFromLeft = null; // 记录从左而来的祖先节点
        while (node != null) {
            if (key < node.key) {
                node = node.left;
            } else if (key > node.key) {
                // 往右边查找,说明这个祖先节点是从左边来的
                ancestorFromLeft = node;
                node = node.right;
            } else {
                break;
            }
        }

        if (node == null) {
            return null; //没找到
        }

        if (node.left != null) {
            //存在左子树,那么就返回左子树中最大的一个值
            return max(node.left);
        }

        // 不存在左子树,那么就找左边来的祖先节点,距离自己最近的一个
        // 因为ancestorFromLeft每次都在更新,所以当前的ancestorFromLeft就是目标值。

        return ancestorFromLeft == null ? null : ancestorFromLeft.value;
    }

9、找到一个节点的后继节点 --- 与8是对称的

  1. 节点有右子树,那么节点就是右子树中的最小值
  2. 节点没有右子树:
    1. 这个节点就是他右边来的祖先的最近一个
    // 找到一个key的后继(就是比他key大的最小的值)
    public Object predecessor(int key) {
        BSTTreeNode node = root;
        BSTTreeNode ancestorFromRight = null;
        while (node != null) {
            if (key < node.key) {
                // 往左边查找,说明这个祖先节点是从右边来的
                ancestorFromRight = node;
                node = node.left;
            } else if (key > node.key) {
                node = node.right;
            } else {
                break;
            }
        }

        if (node == null) {
            return null; //没找到
        }
        if (node.right != null) {
            return min(node.right);
        }
        return ancestorFromRight == null ? null : ancestorFromRight.value;
    }

练习题

LeetCode 450,700,701三题就对应了二叉查找树的增删查。对于删除操作,比较复杂要多加练习。

98. 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

解题思路:

  1. 节点比左孩子大,比右孩子小(不包含等于的情况)
  2. 节点比左子树中最大的值大,比右子树中最小的值小 --- (1就可以合并到2中)
  3. 根据2的条件,递归
public class Test98 {
    public boolean isValidBST(TreeNode root) {
        return dsf(root);
    }

    // 递归方法
    public boolean dsf(TreeNode node) {
        // 节点是空,或者节点是叶子节点,符合二叉搜索树,可以返回true
        if (node == null || (node.left == null && node.right == null)) {
            return true;
        }
        int val = node.val;
        // 定义一个临时变量
        TreeNode tmp = node;
        
        // 到左子树中找最大的值,要求比当前节点值小,否则返回false
        if (tmp.left != null) {
            tmp = tmp.left;
            while (tmp.right != null) {
                tmp = tmp.right;
            }
            if (tmp.val >= val) {
                return false;
            }
        }

        // 到右子树中找最小的值,要求比当前节点值大,否则返回false
        tmp = node;
        if (node.right != null) {
            tmp = tmp.right;
            while (tmp.left != null) {
                tmp = tmp.left;
            }
            if (tmp.val <= val) {
                return false;
            }
        }

        // 递归调用
        return dsf(node.left) && dsf(node.right);
    }
}

938. 二叉搜索树的范围和

给定二叉搜索树的根结点 root,返回值位于范围 [low, high] 之间的所有结点的值的和。

----- 这一题,总的来说还是DSF更好理解,效率更好

解题思路:

  1. 深度优先遍历,因为是二叉查找树,因此树的节点是有顺序的。
  2. 如果当前节点比high大,那么只要在左子树中遍历就可以了
  3. 如果当前节点比low小,那么只要在右子树找那个遍历就可以了
  4. 否则,值就是当前节点的值 加上左子树、右子树中符合要求的节点的和。
class Solution {
    public int rangeSumBST(TreeNode root, int low, int high) {
        if (root == null) {
            return 0;
        }

        if (root.val > high) {
            return rangeSumBST(root.left, low, high);
        }

        if (root.val < low) {
            return rangeSumBST(root.right, low, high);
        }

        return root.val + rangeSumBST(root.left, low, high) + rangeSumBST(root.right, low, high);
    }
}

解题思路2:

  1. 广度优先遍历,同样利用二叉查找树的特性可以进行剪枝
  2. 实现思路和DSF一样,只不过是变成了BSF
  3. 注意一点:
    1. ArrayDeque是能插入null作为节点的
    2. LinkedList可以插入null作为节点
class Solution {
    public int rangeSumBST(TreeNode root, int low, int high) {
        if (root == null) {
            return 0;
        }

        int res = 0;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode poll = queue.poll();
                if (poll == null) {
                    continue;
                }
                if (poll.val > high) {
                    queue.offer(poll.left);
                } else if (poll.val < low) {
                    queue.offer(poll.right);
                } else {
                    res += poll.val;
                    queue.offer(poll.left);
                    queue.offer(poll.right);
                }
            }
        }
        return res;
    }
}

1008. 前序遍历构造二叉搜索树

解题思路:

  1. 前序遍历,第一个值就是根节点,知道了根节点,遍历数组,一个个去插入节点即可。
class Solution {
    public TreeNode bstFromPreorder(int[] preorder) {
        if (preorder.length == 0) {
            return null;
        }
        TreeNode root = new TreeNode(preorder[0]);
        for (int i = 1; i < preorder.length; i++) {
            put(new TreeNode(preorder[i]), root);
        }
        return root;
    }

    public void put(TreeNode add, TreeNode node) {
        if (add == null) {
            return;
        }
        TreeNode preNode = null;
        while (node != null) {
            preNode = node;
            if (node.val > add.val) {
                node = node.left;
            } else if (node.val < add.val) {
                node = node.right;
            } else {
                break;
            }
        }

        if (preNode.val > add.val) {
            preNode.left = add;
            return;
        }

        if (preNode.val < add.val) {
            preNode.right = add;
            return;
        }
    }
}

235. 二叉搜索树的最近公共祖先

解题思路:

  1. 因为是二叉搜索树,因此最近的公共祖先-----转化为,找到最近的一个节点,p 和 q在这个节点的左右两边
  2. 注意,p或者q也可以是公共祖先

递归写法

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) {
            return null;
        }
        TreeNode node = root;

        TreeNode min = p.val > q.val ? q : p;
        TreeNode max = p.val > q.val ? p : q;

        if (node.val >= min.val && node.val <= max.val) {
            return node;
        }

        if (node.val > max.val) {
            return lowestCommonAncestor(node.left, p, q);
        } else {
            return lowestCommonAncestor(node.right, p, q);
        }
    }
}

非递归写法

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        TreeNode node = root;

        TreeNode min = p.val > q.val ? q : p;
        TreeNode max = p.val > q.val ? p : q;

        while (true) {
            if (node.val >= min.val && node.val <= max.val) {
                return node;
            } else if (node.val > max.val) {
                node = node.left;
            } else {
                node = node.right;
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值