二叉树常见题目及C++实现

题目一 实现二叉树先序中序后序遍历,包括递归和非递归实现

  关于三种遍历方式的递归在数据结构的课本上应该写的很清楚,理解起来很简单,没有多做总结。
  通过看左神视频多了一点理解,在二叉树的递归遍历中,因为递归实现本质上是个系统栈,所以每个节点都会访问到三次,包括刚刚访问到当前节点,递归当前节点左子树,递归当前节点右子树。
举例说明:如图1,如果采用先根遍历,访问节点顺序为1,2,4,null,4,null,4,2,5,null,5,null,5,2,1,3,6,null,6,null,6,3,null,3,1,去掉null之后整个访问顺序为:1,2,4,4,4,2,5,5,5,2,1,3,6,6,6,3,1,每个数字都会被访问3次,每个节点被访问第一次的时候输出就是先根遍历1,2,4,5,3,6,被访问第二次的时候输出就是中根遍历4,2,5,1,6,3,被访问第三次的时候输出就是后根遍历4,5,2,6,3,1。
  关于非递归实现的思路顺便提几句

  • 先根遍历【根左右】—借助一个栈,首先将根节点入栈,循环中,在栈非空的情况下,首先打印栈顶元素,然后对于栈顶元素,有右孩子压入右孩子,有左孩子压入左孩子。【栈先进后出,所以出栈的时候就会变成根左右】
  • 中根遍历【左根右】— 同样借助一个栈,当前节点不为空,将其左节点压到栈中,节点向左。当前节点为空,从栈中拿出节点,打印,当前节点往右走。【代码参照左老师代码,循环逻辑很有意思】
  • 后根遍历【左右根】— 后根遍历的实现,如果直接非递归实现,当从栈中弹出节点时,且该节点又有右子节点时,需要再次尝试先遍历该右子节点的所有左子节点时,会将该节点丢失。那么,这时需要重新将该节点压栈,但是当再次弹出时,需要区分其右子节点是否已经遍历过了。有关于此实现的一段伪代码,引入了一个成员变量判断是否被访问过。
    如果按照根右左的顺序遍历二叉树,最后反过来其实是左右根,因此按照根左右的顺序遍历二叉树,在该打印节点的时候,将节点的值存入栈中,最后弹出,就可以实现了。如第三个图所示。
  • 最后层序遍历,使用队列来实现,第四个图简单分析了一下为什么用队列可以实现。就是对于当前节点有左孩子左孩子入队,有右孩子右孩子入队。
    加粗样式在这里插入图片描述
            图1                       图2
    在这里插入图片描述
                          图3
    在这里插入图片描述
                            图4
#include <iostream>
#include <stack>
#include <queue>

using namespace std;

struct Node{
    int value;
    Node *leftChild;
    Node *rightChild;
};

class Traversal{
public:
    /**先根遍历,递归实现**/
    void preOrderTraversal(Node *root)
    {
        if(root == nullptr)
            return ;
        cout<<root->value<<" ";
        if(root->leftChild)
            preOrderTraversal(root->leftChild);
        if(root->rightChild)
            preOrderTraversal(root->rightChild);
    }
    /**先根遍历,非递归实现,有右先压右,有左后压左**/
    void preOrderTraversal_Non(Node *root)
    {
        if(root == nullptr)
            return;
        stack<Node*> st;
        st.push(root);
        while(!st.empty())
        {
            root = st.top();
            st.pop();
            cout<<root->value<<" ";
            if(root->rightChild)
                st.push(root->rightChild);
            if(root->leftChild)
                st.push(root->leftChild);
        }
    }

    /**中根遍历,递归实现**/
    void midOrderTraversal(Node *root)
    {
        if(root == nullptr)
            return ;
        if(root->leftChild)
            midOrderTraversal(root->leftChild);
        cout<<root->value<<" ";
        if(root->rightChild)
            midOrderTraversal(root->rightChild);
    }
    /**中根遍历,非递归实现**/
    void midOrderTraversal_Non(Node *root)
    {
        if(root == nullptr)
            return;
        stack<Node*> st;
        while(!st.empty() || root != nullptr)
        {
            if(root != nullptr)
            {
                st.push(root);
                root = root->leftChild;
            }else
            {
                root = st.top();
                st.pop();
                cout<<root->value<<" ";
                //root的右孩子如果是空,不要重新压入新的值,从栈中取值即可
                root = root->rightChild;
            }

        }
    }

    /**后根遍历,递归实现**/
    void posOrderTraversal(Node *root)
    {
        if(root == nullptr)
            return ;
        if(root->leftChild)
            posOrderTraversal(root->leftChild);
        if(root->rightChild)
            posOrderTraversal(root->rightChild);
        cout<<root->value<<" ";
    }
    /**后根遍历,非递归实现**/
    void posOrderTraversal_Non(Node *root)
    {
        if(root == nullptr)
            return;
        if(root == nullptr)
            return;
        stack<Node*> st;
        st.push(root);
        stack<int> tmp;
        while(!st.empty())
        {
            root = st.top();
            st.pop();
            tmp.push(root->value);
            if(root->leftChild)
                st.push(root->leftChild);
            if(root->rightChild)
                st.push(root->rightChild);
        }
        while(!tmp.empty())
        {
            cout<< tmp.top() <<" ";
            tmp.pop();
        }
        cout<<endl;
    }
    /**层序遍历**/
    void levelTraversal(Node *root)
    {
        if(root == nullptr)
            return ;
        queue<Node *> qu;
        qu.push(root);
        while(!qu.empty())
        {
            root = qu.front();
            qu.pop();
            cout<<root->value<<" ";
            if(root->leftChild)
                qu.push(root->leftChild);
            if(root->rightChild)
                qu.push(root->rightChild);
        }
    }
};

int main()
{
    cout << "Hello world!" << endl;
    Node *root = new Node;
    root->value = 1;
    root->leftChild = new Node;
    root->leftChild->value = 2;
    root->rightChild = new Node;
    root->rightChild->value =3;
    root->leftChild->leftChild = new Node;
    root->leftChild->leftChild->value = 4;
    root->leftChild->rightChild = new Node;
    root->leftChild->rightChild->value = 5;
    root->rightChild->leftChild = new Node;
    root->rightChild->leftChild->value = 6;
    root->leftChild->leftChild->leftChild = nullptr;
    root->leftChild->leftChild->rightChild = nullptr;
    root->leftChild->rightChild->leftChild = nullptr;
    root->leftChild->rightChild->rightChild = nullptr;
    root->rightChild->leftChild->leftChild = nullptr;
    root->rightChild->leftChild->rightChild = nullptr;
    root->rightChild->rightChild = nullptr;

    Traversal tr;
    cout<<"先根遍历(递归)"<<endl;
    tr.preOrderTraversal(root);
    cout<<"\n"<<"先根遍历"<<endl;
    tr.preOrderTraversal_Non(root);
    cout<<"\n---------------------------------------"<<endl;
    cout<<"\n"<<"中根遍历(递归)"<<endl;
    tr.midOrderTraversal(root);
    cout<<"\n"<<"中根遍历"<<endl;
    tr.midOrderTraversal_Non(root);
    cout<<"\n---------------------------------------"<<endl;
    cout<<"\n"<<"后根遍历(递归)"<<endl;
    tr.posOrderTraversal(root);
    cout<<"\n"<<"后根遍历"<<endl;
    tr.posOrderTraversal_Non(root);
    cout<<"\n---------------------------------------"<<endl;
    cout<<"\n"<<"层序遍历"<<endl;
    tr.levelTraversal(root);
    return 0;
}

题目二:如何直观地打印一颗二叉树

class printBinaryTree{
public:
    void printTree(Node *root)
    {
        cout<<"Print Binary Tree:"<<endl;
        printInOrder(root,0,"H",17);
        cout<<endl;
    }
    void printInOrder(Node *root,int height,string to,int len)
    {
        if(root == nullptr)
            return;
        printInOrder(root->rightChild,height+1,"v",len);
        string val = to + to_string(root->value) + to;
		int lenM = val.size();
		int lenL = (len - lenM) / 2;
		int lenR = len - lenM - lenL;
		val = getSpace(lenL) + val + getSpace(lenR);
		cout<<getSpace(height * len) + val<<endl;
		printInOrder(root->leftChild, height + 1, "^", len);
    }
    string getSpace(int num)
    {
		string space = " ";
		string buf;
		for (int i = 0; i < num; i++) {
			buf.append(space);
		}
		return buf;
	}
};

在这里插入图片描述
  打印题目1中树的情况,逆时针90度观察可以获得空间概念的二叉树,^ ^表示其父节点为左上,v v表示父节点为左下,这样如果某一棵二叉树的节点都是一个值,也可以直观地观察到二叉树的形状。

题目三:在二叉树中找到某一个节点的后继节点

后继节点:在二叉树的中序遍历的序列中, node的下一个节点叫作node的后继节点。】
【题目】 现在有一种新的二叉树节点类型如下:

public class Node { 
	public int value; 
	public Node left; 
	public Node right; 
	public Node parent; 
	public Node(int data) 
	{ 
		this.value = data; 
	} 
}

  该结构比普通二叉树节点结构多了一个指向父节点的parent指针。假设有一 棵Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头节点的parent指向null。只给一个在二叉树中的某个节点node,请实现返回node的后继节点的函数。
  时间复杂度:时间复杂度为节点个数【直接中序遍历】也可以实现该功能,即O(N),但是最优的时间复杂度应该为一个节点到其后继节点的距离。
  如图一所示,该结构的二叉树,中序遍历结果为:4,2,5,1,6,3。要找这里面任何一个节点的后继节点,首先分析会遇到哪些情况。

  • 如果一个节点有右节点,根据左根右,一定会进入到该节点的右子树找它的后继节点,如节点1,2,1的后继节点为6,2的后继节点为5,可以分析出如果一个节点有右节点,那么他的后继节点一定是右子树的最左节点。
  • 如果该节点没有右节点,因为没有右节点,所以该节点的左根部分已经遍历完成,如4,5,3,6,这时需要分析这个节点是不是其父节点的左孩子,如果是,说明父节点的左树遍历完成,根据左根右需要访问父节点了,此时返回父节点;如果这个节点不是其父节点的左孩子,说明以父节点为根的子树已经遍历完成,需要分析父节点是不是其父节点的左孩子,循环向上。

根据此思路同时写了找到一个节点前驱节点的代码,实质是一样的。

#include <iostream>
#include <queue>

using namespace std;

struct ParentNode{
    int value;
    ParentNode *leftChild;
    ParentNode *rightChild;
    ParentNode *parent;
};

class getSuccessorNode{
public:
    /**层序遍历,打印节点**/
    void levelTraversal(ParentNode *root)
    {
        if(root == nullptr)
            return ;
        queue<ParentNode *> qu;
        qu.push(root);
        while(!qu.empty())
        {
            root = qu.front();
            qu.pop();
            cout<<"value is: "<<root->value<<" ";
            if(root->parent != nullptr)
                cout<<"parent node is: "<<root->parent->value<<". "<<endl;
            else
                cout<<"Root!"<<endl;
            if(root->leftChild)
                qu.push(root->leftChild);
            if(root->rightChild)
                qu.push(root->rightChild);
        }
    }
    ParentNode* getNextNode(ParentNode *node)
    {
        if(node == nullptr)
            return nullptr;
        ParentNode *tmp = node;
        if(tmp->rightChild != nullptr)
        {
            return getLeftest(tmp->rightChild);
        }else
        {
             while(tmp->parent != nullptr)
            {
                if(tmp == tmp->parent->leftChild)
                    return tmp->parent;
                else
                    tmp = tmp->parent;
            }
            return nullptr;
        }
    }
    ParentNode* getLeftest(ParentNode *node)
    {
        if(node == nullptr)
            return nullptr;
        while(node->leftChild != nullptr)
        {
            node = node->leftChild;
        }
        return node;
    }

    /**找某个节点的前驱节点**/
    ParentNode* getPreviousNode(ParentNode *node)
    {
        if(node == nullptr)
            return nullptr;
        ParentNode *tmp = node;
        if(tmp->leftChild)//找到左孩子的最右节点
        {
            return getRightest(tmp->leftChild);
        }else
        {
            while(tmp->parent != nullptr)
            {
                if(tmp == tmp->parent->rightChild)
                    return tmp->parent;
                else
                    tmp = tmp->parent;
            }
            return nullptr;
        }
    }
     ParentNode* getRightest(ParentNode *node)
    {
        if(node == nullptr)
            return nullptr;
        while(node->rightChild != nullptr)
        {
            node = node->rightChild;
        }
        return node;
    }

};

在这里插入图片描述

题目四 二叉树的序列化和反序列化

  二叉树是存在内存中的,如果下一次需要拿出二叉树复用,能不能直接从字符串中读取恢复二叉树呢?这就是这个题目的意义,将二叉树以序列的方式存储,然后用的时候以反序列化复用。跟编解码类似,规定编解码规则即可。
  例如,对于一个二叉树,我们希望读取数据流的过程中就在从头开始构建,因为先序遍历是根左右遍历,所以以先序遍历的结果存储,如果遇到nullptr,就以特殊字符代替,例如‘$’,同时二叉树中每个节点之间也需要以其他特殊字符分割开来。这样反序列化的时候根据特殊字符就可以重构了。
【实现包括先序和层序序列化和反序列化,层序遍历也是从根开始的。】

class SerializeTree{
public:
    /**先序序列化**/
    string serializeTree(Node *root)
    {
        if(root == nullptr)
            return "$_";
        string res = to_string(root->value)+"_";
        res = res + serializeTree(root->leftChild);
        res = res + serializeTree(root->rightChild);
        return res;
    }
    Node* reserializeTree(string serial)
    {
        vector<string> res ;
        SplitString(serial,res,"_");
        queue<string> que;
        for(int i=0;i<res.size();i++)
        {
            que.push(res[i]);
        }
        return reconTree(que);
    }
    /**又是因为引用&符号出错,如果没有引用,每一次函数修改que,栈中会保存每个递归的
    参数,这样递归回去的时候还是没有pop的的queue,所以会出错
    为什么Java没事呢**/
    Node* reconTree(queue<string> &que)
    {
        string val = que.front();
        que.pop();
        queue<string> ss = que;
        if(val == "$")
        {
            return nullptr;
        }
        Node *root = new Node;
        root->value = atoi(val.c_str());
        root->leftChild = reconTree(que);
        root->rightChild = reconTree(que);
        return root;
    }
    /**层序序列化**/
    string levelSerial(Node *root)
    {
        if(root == nullptr)
            return "$_";
        queue<Node*> que;
        Node *tmp;
        que.push(root);
        string res = "";
        while(!que.empty())
        {
            tmp = que.front();
            que.pop();
            if(tmp != nullptr)
            {
            	res = res + to_string(tmp->value)+"_";
            	que.push(tmp->leftChild);
            	que.push(tmp->rightChild);
            }
            else
            {
            	res = res + "$_";
			}
        }
        return res;
    }
    Node* levelReconTree(string serial)
    {
        vector<string> res ;
        SplitString(serial,res,"_");
        queue<Node*> que;
        Node *root = generateNode(res[0]);
        if(root !=  nullptr)
            que.push(root);
        Node *node = nullptr;
        int i = 1;
        while(!que.empty())
        {
            node = que.front();
            que.pop();
            node->leftChild = generateNode(res[i++]);
            node->rightChild = generateNode(res[i++]);
            if(node->leftChild != nullptr)
                que.push(node->leftChild);
            if(node->rightChild != nullptr)
                que.push(node->rightChild);
        }
        return root;
    }
    Node* generateNode(string s)
    {
        if(s == "$")
            return nullptr;
        Node *root= new Node;
        root->value = atoi(s.c_str());
        root->leftChild = nullptr;
        root->rightChild = nullptr;
        return root;
    }

    void SplitString(const string& s, vector<string>& v, const string& c)
    {
        string::size_type pos1, pos2;
        pos2 = s.find(c);
        pos1 = 0;
        while(string::npos != pos2)
        {
            v.push_back(s.substr(pos1, pos2-pos1));

            pos1 = pos2 + c.size();
            pos2 = s.find(c, pos1);
        }
        if(pos1 != s.length())
            v.push_back(s.substr(pos1));
    }
};

题目五:折纸问题

【题目】 请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。如果从纸条的下边向上方连续对折 2 次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。
给定一个输入参数N,代表纸条都从下边向上方连续对折N次, 请从上到下打印所有折痕的方向。 例如:N=1时,打印: down N=2时,打印: down down up
  注意折纸的方向都是从下向上折的,每次对折之后,关于之前的折痕,都会是折痕以上为down,折痕以上为up。以下图为例说明,图片来自王同学。
在这里插入图片描述
第一次对折,为黑线1.折痕为down,相对于黑线来说,下一次对折,黑线上面部分折痕为down,也就是菊线down2,黑线下面部分折痕为up,也就是菊线2up。下一次对折相对于菊线,上面部分为down,下面部分为up,展开之后会发现相对于两条菊线,上下新增的两条就是down和up。
也就是如下图所示,每一个新的折痕它的新增折痕都是相对于它先下后上,整个的遍历过程为将所有节点压缩到一条线上从左到右遍历,即二叉树的中序遍历,关于遍历第一题总结了,不再写了。
在这里插入图片描述

题目六:判断一棵二叉树是否是平衡二叉树

平衡二叉树:任何一棵树的左子树与右子树的高度差不超过1.
这里用递归函数很好用,递归会到达一个节点三次,先访问节点,然后左子树一次,右子树一次
如果以每个节点为头的树都是平衡的,这棵树是平衡的,每次递归需要判断以下几个问题,最后返回左右子树的高度差,如果不超过1就是平衡二叉树。

  • 左树是否平衡
  • 右树是否平衡
  • 左树平衡,左树的高度
  • 右树平衡,右树的高度

这里有三种实现方法,三种方法的实质都是一样的。第一种最麻烦,是根据视频学习总结的关于递归函数的涉及通用思路来写的,第2 3种实现更简单一些。
第一种,通用设计思路

  1. 列出所有可能性【上面列出的四种】
  2. 整理返回值类型【需要返回子树是否平衡,以及平衡情况下子树的高度】
  3. 整个递归过程按照同样的结构返回子树的信息
  4. 整合子树的信息,得到自己需要的信息
  5. 向上返回
/**根据需要返回子树是否平衡,以及平衡情况下子树的高度,设计的返回结构**/
class isBan{
public:
    bool isBanlance;
    int depth;
    isBan()
    {
        isBanlance = false;
        depth = 0;
    }
    isBan(bool b,int d)
    {
        isBanlance = b;
        depth = d;
    }
};
class BanlancedTree {
public:

    bool isBalanced(Node *root) {
        if(root == nullptr)
            return true;
        return isBalancedHelper(root)->isBanlance;
    }
    isBan* isBalancedHelper(Node *root)
    {
        if(!root)
        {
            return new isBan(true,0);
        }
        isBan* left = isBalancedHelper(root->leftChild);
        if(left->isBanlance == false)
            return new isBan(false,0);
        isBan* right = isBalancedHelper(root->rightChild);
        if(right->isBanlance == false)
            return new isBan(false,0);
        if(abs(left->depth - right->depth)>1)
        {
            return new isBan(false,0);
        }
        return new isBan(true,max(left->depth,right->depth)+1);
    }
};

以下两种实现方法,第一种在函数中只返回树是否为平衡二叉树,然后输入depth二叉树深度变量,并在递归的过程中改变,得到每次递归左右子树的差值,从而判断左右是否平衡。第2种方法更简单一点,只利用一个int返回值,如果不平衡返回负值,平衡返回子树高度,代码更简洁,复用性不高。

/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isBalanced(TreeNode *root) {
       int depth = 0;
       return isBalancedHelper(root,depth);
    }
    bool isBalancedHelper(TreeNode *root,int &depth)
    {
        if(!root)
        {
            depth = 0;
            return true;
        }
        int left,right;
        if(isBalancedHelper(root->left,left) && isBalancedHelper(root->right,right))
        {
            if(abs(left-right) <=1)
            {
                depth = 1 + max(left,right);
                return true;
            }
        }
        return false;
    }
};
/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isBalanced(TreeNode *root) {
       if(!root) return true;
       return (helper(root) != -1);
    }
    int helper(TreeNode *root)
    {
        if(!root) return 0;
        int left,right;
        left = helper(root->left);
        if(left == -1)
            return -1;
        right = helper(root->right);
        if(right == -1)
            return -1;
        if(abs(left-right)>1)
            return -1;
        return 1+max(left,right);
    }
};

题目七:判断一棵树是否是搜索二叉树、判断一棵树是否是完全二叉树

搜索二叉树:任何一个节点,左子树上的值都比他的值小,右子树上所有的值都比它的值大
由定义可以想到,如果二叉树中序遍历是依次升序的 ,就是搜索二叉树【一般不出现重复节点,重复节点可以压缩到一个小的结构上】
在中序遍历的过程中保证升序,在非递归版本的中序遍历中进行修改

完全二叉树
堆的结构,堆是完全二叉树,将二叉树按层遍历,

  1. 如果一个节点有右孩子没有左孩子,直接返回false
  2. 如果一个节点不是左右两个孩子都全,或者有左没右,或者左右都没有
    后面遇到的所有节点都必须是叶结点,否则不是完全二叉树
class judgeTree{
public:
    bool isSearchBiTree(Node *root)
    {
        if(root == nullptr)
            return true;
        stack<Node*> s;
        int tmp = INT_MIN;
        while(!s.empty() || root != nullptr)
        {
            if(root != nullptr)
            {
                s.push(root);
                root = root->leftChild;
            }else
            {
                root = s.top();
                s.pop();
                if(root->value > tmp)
                    tmp = root->value;
                else
                    return false;
                root = root->rightChild;
            }
        }
        return true;
    }
    bool isCompleteBiTree(Node *root)
    {
        if(root == nullptr)
            return true;
        queue<Node*> que;
        que.push(root);
        bool leaf = false;
        Node *left;
        Node *right;
        while(!que.empty())
        {
            root = que.front();
            que.pop();
            left = root->leftChild;
            right = root->rightChild;
            if((leaf && (left != nullptr || right != nullptr)) || (left == nullptr && right != nullptr))
            {
                return false;
            }
            if(left)
            {
                que.push(left);
            }
            if(right)
            {
                que.push(right);
            }else
            {
                leaf = true;
            }
        }
        return true;
    }
};

题目八:已知一棵完全二叉树,求其节点的个数

要求:时间复杂度低于O(N),N为这棵树的节点个数
如果遍历整棵二叉树,时间复杂度为严格O(N),因为该树是完全二叉树,也就是叶子节点只在最后一层,所以可以利用满二叉树的性质进行加速。
思路:首先遍历左边界,一路走到最左边节点,可以得到该二叉树的层数depth,然后遍历右子树的左边界,如果层数也是depth,说明左子树的满二叉树,下一步递归求右子树节点个数即可。如果右子树的左边界没有到达最后一层,说明右子树是depth-1层的满二叉树,下一步递归求左子树节点个数即可。

int nodeNums(Node *root)
{
    if(root == nullptr)
        return 0;
    int depth = Depth(root);
    int rightD = Depth(root->rightChild)+1;//右子树的左边界
    if(depth == rightD)
    {
        //左子树是满的,递归右子树
        return nodeNums(root->rightChild)+1+pow(2,depth-1)-1;
    }else
    {
        //右子树是满的递归左子树
        return nodeNums(root->leftChild)+1+pow(2,depth-2)-1;
    }

}
int Depth(Node *root)
{
    if(root == nullptr)
        return 0;
    int depth = 0;
    while(root != nullptr)
    {
        depth++;
        root = root->leftChild;
    }
    return depth;
}
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值