代码随想录DAY18 - 二叉树 - 08/17

news2024/9/21 10:25:44

目录

二叉搜索树的最小绝对差

题干

思路和代码

方法一:求中序序列

方法二:递归法+双指针法

方法三:迭代法+双指针法

二叉搜索树中的众数

题干

思路和代码

方法一:求中序序列

方法二:递归法+双指针+中序遍历

​编辑

方法三:迭代法+双指针+中序遍历

二叉树的公共祖先

题干

思路和代码

​编辑

递归法:后序遍历

递归法:优化

总结:二叉搜索树


二叉搜索树的最小绝对差

题干

题目:给你一个二叉搜索树的根节点 root ,返回树中任意两不同节点值之间的最小差值 。差值是一个正数,等于两值之差的绝对值。

注意:0 <= Node.val <= 10^5(节点值非负数)

链接:. - 力扣(LeetCode)

思路和代码

题目是要求 “任意” 两个结点的差值,但问题在于如何遍历任意两个结点?

二叉搜索树的结点是有序的,最小差值只会出现在中序序列的两个相邻元素之间。

方法一:求中序序列

先求二叉搜索树的中序序列数组,再遍历数组从中找差值最小的。而二叉搜索树的结点是有序的,最小差值只会出现在两个相邻元素之间,因此只需要遍历一次数组即可。

class Solution {
public:
    void inorder(TreeNode* root, vector<int> &nums){
        if (root == nullptr) return;
        inorder(root->left,nums);
        nums.push_back(root->val);
        inorder(root->right,nums);
    }
    int getMinimumDifference(TreeNode* root) {
        vector<int> nums;
        inorder(root,nums);
        int minValue = INT_MAX; // 记录最小差值
        // 遍历中序序列数组,只需要比较相邻两个元素即可
        for (int i = 0; i < nums.size()-1; ++i) {
            if (nums[i+1] - nums[i] < minValue){
                minValue = nums[i+1] - nums[i];
            }
        }
        return minValue;
    }
};
方法二:递归法+双指针法

之前的方法是先将中序序列数组求出,再遍历数组,这样相当于遍历了两次二叉树的所有结点。但其实在中序遍历的过程中就可以直接记录最小差值,这样就只需要遍历一次二叉树的所有结点,省时间。

问题是如何在中序遍历的过程中记录最小差值?

我们知道最小差值只会出现在中序序列的两个相邻元素之间,在数组里可以直接使用下标访问相邻元素,而在树中,我们需要设置两个指针,一个指针 cur 指向当前遍历的结点,另一个指针 pre 指向 cur 的上一个结点,这样 pre 和 cur 所指的两个元素就是相邻的。

  • 递归参数和返回值:参数是传入的当前结点,无返回值。

  • 递归结束的条件:若传入的结点为空,说明已经遍历到最底部,则直接返回。

  • 递归顺序:按照 “左中右” 的顺序,我们需要先不断遍历左子树找到最左结点。当 cur 指向最左结点时,pre 暂时为空,则需要让 pre = cur,待 cur 回溯到父节点后,才能比较 pre 和 cur 的差值 与 最小差值,最后再遍历右子树。

class Solution {
public:
    int minValue = INT_MAX; // 记录最小差值
    TreeNode* pre = nullptr; // 记录上一个结点指针
    // 在每一次递归中都要更新,因此要定义为全局变量
    void inorder(TreeNode* cur){
        // 当前结点为空
        if (cur == nullptr) return;
        
        inorder(cur->left); // 遍历左子树
        
        if (pre != nullptr){
            minValue = min(abs(pre->val - cur->val),minValue);
        }
        pre = cur;
        
        inorder(cur->right); // 遍历右子树
    }

    int getMinimumDifference(TreeNode* root) {
        inorder(root);
        return minValue;
    }
};
方法三:迭代法+双指针法

思路和方法二一样,只是将递归中序遍历换成了迭代法。

class Solution {
public:
    int getMinimumDifference(TreeNode* root) {
        stack<TreeNode*> tmpNode; // 存储遍历过的序列
        TreeNode* cur = root;
        TreeNode* pre = nullptr; // 记录上一个结点
        int minValue = INT_MAX;
        while (cur || !tmpNode.empty()){
            if (cur == nullptr){
                // 已经找到最左结点
                cur = tmpNode.top();
                tmpNode.pop();
                // ....计算 pre 和 cur 的差值
                if (pre != nullptr){
                    minValue = min(abs(pre->val - cur->val),minValue);
                }
                pre = cur;
                // ....
                cur = cur->right; // 遍历右子树
            } else{
                tmpNode.push(cur); // 暂存插入的结点
                cur = cur->left; // 不断遍历左子树直到找到最左结点
            }
        }
        return minValue;
    }
};

二叉搜索树中的众数

题干

题目:给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值

  • 结点右子树中所含节点的值 大于等于 当前节点的值

  • 左子树和右子树都是二叉搜索树

链接:. - 力扣(LeetCode)

思路和代码

方法一:求中序序列

由于二叉搜索树的有序性,可以先求出其中序序列数组,再遍历数组统计出现频率最高的元素。

注意,在遍历数组统计元素次数时,我们使用了双指针法,即快慢指针法,用慢指针 slow 记录当前元素出现的第一个位置,快指针 fast 记录当前元素出现的最后一个位置,这样当前元素出现的次数就等于 fast - slow + 1。 每统计完一个元素的次数才更新结果集。

class Solution {
public:
    // 求中序序列
    void inorder(TreeNode* node, vector<int> &nums){
        if (node == nullptr) return;
        inorder(node->left,nums);
        nums.push_back(node->val);
        inorder(node->right,nums);
    }
    // 找众数
    vector<int> findMode(TreeNode* root) {
        vector<int> nums; // 存储中序序列
        inorder(root,nums); // 求中序序列
        int maxCount = 0; // 记录最大出现次数
        vector<int> result; // 存储众数结果集
        // 双指针法遍历数组,统计每个数出现的频率
        int slow = 0; // slow 指向相同元素的起始位置
        int fast; // fast 指向相同元素的末尾位置
        for (fast = 0; fast < nums.size(); ++fast) {
            if (fast == nums.size()-1 || nums[slow] != nums[fast+1]){
                // 当 fast 遍历到数组末尾,或者 fast 的下一个元素已经是不同元素时
                // 说明当前元素已经遍历到末尾
                int count = fast-slow+1; // 统计当前元素的出现次数
                if (maxCount < count){
                    maxCount = count;
                    result.clear(); // 当出现频率更高的元素时,结果集数组要先清空
                    result.push_back(nums[slow]);
                } else if (maxCount == count){
                    result.push_back(nums[slow]);
                }
                slow = fast+1;
            }
        }
        return result;
    }
};
方法二:递归法+双指针+中序遍历

同样是要用双指针法,一个指针 cur 指向当前结点,另一个指针 pre 指向上一个结点,用 curCount 统计当前结点出现的次数,用 maxCount 统计最大出现次数,用 result 数组统计众数结果集。这里的双指针法和上一个方法中在有序数组里的双指针法不同。

在上一个方法有序数组中,每统计完一个元素的次数才更新 result 。但是在递归遍历中,只要当前结点的出现次数大于或等于 maxCount了,就立即更新 result,哪怕此时当前节点的出现次数还没统计完毕。

  • 递归参数和返回值:参数是传入的当前结点 cur 以及结果集数组 result (在递归过程中不断更新 result)。无返回值。

  • 递归结束条件:当 cur 为空,要向上层回溯,递归结束。

  • 递归顺序:根据中序遍历 “左中右” 的顺序,先遍历左子树统计元素次数,再回到中间结点处理,而后遍历右子树统计元素次数。

class Solution {
public:
    int maxCount = 0; // 记录最大出现次数
    int curCount = 0; // 记录当前元素的出现次数
    TreeNode* pre = nullptr; // 记录上一个元素
    void count(TreeNode* cur, vector<int> &result){
        if (cur == nullptr) return;
        
        count(cur->left,result); // 遍历左子树
        
        if (pre == nullptr){ // 如果 pre 为空,说明 cur 为中序序列的第一个结点,此时 count 为 1
            curCount = 1;
        } else if (pre->val == cur->val){
            curCount++;
        } else {
            curCount = 1;
        }

        if (curCount > maxCount){
            maxCount = curCount;
            result.clear();
            result.push_back(cur->val);
        } else if (curCount == maxCount){
            result.push_back(cur->val);
        }
        
        pre = cur;
        count(cur->right,result); // 遍历右子树
    }
    vector<int> findMode(TreeNode* root) {
        vector<int> result;
        count(root,result);
        return result;
    }
};
方法三:迭代法+双指针+中序遍历

思路和上述方法相同,都是在中序遍历过程中记录众数,只不过递归中序遍历改成了迭代中序遍历。

class Solution {
public:
    vector<int> findMode(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> tmpNode;
        TreeNode* pre = nullptr; // 记录 cur 的上一个结点
        TreeNode* cur = root;
        int maxCount = 0; // 记录最大出现次数
        int count = 0; // 记录当前结点的出现次数
        while (cur != nullptr || !tmpNode.empty()){
            if (cur != nullptr){
                tmpNode.push(cur);
                cur = cur->left; // 不断向左子树遍历,找到最左边结点
            } else{
                cur = tmpNode.top();
                tmpNode.pop();
                if (pre == nullptr){
                    count = 1; // 如果 pre 为空,说明当前结点是中序序列的第一个结点,count = 1
                } else if (pre->val == cur->val){
                    count++; // 当前结点和之前的结点相同,则结点出现次数递增
                } else{
                    // 当前结点和之前的结点不同,所以 count 更新为 1
                    count = 1;
                }
                // 每遍历到一个结点就比较一次
                // 比较当前结点的出现次数和最大出现频率
                if (count > maxCount){
                    maxCount = count;
                    result.clear();
                    result.push_back(cur->val);
                } else if (count == maxCount){
                    result.push_back(cur->val);
                }
                
                pre = cur;
                cur = cur->right; // 遍历右子树
            }
        }

        return result;
    }
};

二叉树的公共祖先

题干

题目:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

说明:

  • 所有节点的值都是唯一的。

  • p、q 为不同节点且均存在于给定的二叉树中。

链接:. - 力扣(LeetCode)

思路和代码

公共祖先的情况有两种:

  • 情况一:两个结点不在同一棵子树中,则需要回溯找到两个结点的公共祖先。

  • 情况二:一个结点在另一个结点的子树中,则其中一个结点即为最近公共祖先。

递归法:后序遍历

Q:为什么是后序遍历?

先遍历左右子树,看当前子树是否有p或q结点,并把情况返回给父节点(需要收集左右子树的信息)。当左右子树或当前父节点都存在 p、q ,则说明已经找到公共祖先,且由于是后序遍历,此公共祖先肯定是最近的,符合题意。

  • 递归参数和返回值:递归参数是当前结点 node 和要找的 p、q 结点;返回值是 bool 型变量,返回以当前 node 结点为根的子树是否有 p 或 q。

  • 递归结束的条件:当 node 结点为空,说明没有 p、q,直接返回false。

  • 递归顺序:先递归查询左右子树是否有 p、q,此时有多种情况。

    • 左右子树都返回 true,说明左右子树都有 p、q,则当前的父节点即公共祖先(情况一),直接返回 true;

    • 左右子树一个 true、一个 false,说明只有其中一棵子树有p或q,则再判断当前父结点是否为 p 或 q。

      • 若父节点为 p 或 q,则父节点就是公共祖先(情况二),直接返回 true。

      • 若父节点不是 p 或 q,那么仍需要向上层返回,继续寻找公共祖先,不做处理。

    • 左右子树都为 false,再判断当前父节点是否为 p 或 q,若是,直接返回 true。

    • 其余情况均没有找到公共祖先,都需要继续往上返回,返回 leftTree||rightTree(只要有一个子树有 p 或 q,就为 true)。

      (注: leftTree 表示左子树是否有 p 或 q,rightTree 表示右子树是否有 p 或 q)

class Solution {
public:
    TreeNode* ancestor = nullptr; // 记录最近公共祖先,全局变量
    bool findAncestor(TreeNode* node, TreeNode* p, TreeNode* q){
        if (node == nullptr) return false;

        bool leftTree = findAncestor(node->left,p,q);
        if (ancestor != nullptr) return true; // 只要找到了公共祖先,就直接返回 true
        bool rightTree = findAncestor(node->right,p,q);
        if (ancestor != nullptr) return true;

        if (leftTree && rightTree && ancestor == nullptr){
            // 左右子树都为 true,则都有 p、q
            ancestor = node;
            return true;
        } else if ((leftTree && !rightTree) ||
                (!leftTree && rightTree) ){
            // 左子树有,右子树没有;或左子树没有,右子树有
            if (ancestor == nullptr && (node == p || node == q)){
                ancestor = node;
                return true;
            }
        } else{
            // 左右子树都没有,但如果当前结点有,直接返回 true
            if (node == p || node == q) return true;
        }
        return leftTree||rightTree;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        bool result = findAncestor(root,p,q);
        return ancestor;
    }
};
递归法:优化

思路和之前的方法是一样的,只不过我们在递归返回值上做出了调整。由于我们需要记录公共祖先,则可以设定返回值为树的结点。我们在上一个方法中,设定 bool 型变量记录子树中是否有 p、q 结点。在这个方法里,当子树有 p、q 结点时,我们直接返回结点。如果返回结点仍为空,说明还没有找到 p、q。

  • 递归参数和返回值:参数是根节点,要查询的 p、q 结点。返回值是结点。

  • 递归结束条件:当传入的结点为空,说明遍历到最底部,要想上层返回,递归结束。

  • 递归顺序:先遍历左子树查询 p、q,再遍历右子树查询 p、q,并记录左右子树返回的结点情况。

    • 先判断当前结点 root 是否为 p或q,若是,则直接返回 root。(为什么?如下解释)

      • 当结点 root 为 p 或 q,子树若有 p或q,root 是公共祖先,要返回 root。

      • 当结点 root 为 p 或 q,子树没有 p或q,要返回 root,表示找到了 p、q。

    • 若当前结点 root 不是 p 或 q,则有多种情况。

      • 左子树和右子树返回的都不是空指针,说明左右子树都有 p、q,当前结点 node 即为公共祖先,直接返回。

      • 左子树返回空,右子树返回非空,说明右子树有 p 或 q,当前结点 node 又不是 p、q,直接返回右子树的返回结点。

      • 右子树返回空,左子树返回非空,说明左子树有 p 或 q,当前结点 node 又不是 p、q,直接返回左子树的返回结点。

      • 左右子树都返回空,说明一直没有找到 p、q,返回空指针 nullptr。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == nullptr) return nullptr;
        
        TreeNode* leftTree = lowestCommonAncestor(root->left,p,q);
        TreeNode* rightTree = lowestCommonAncestor(root->right,p,q);
        // 只要当前结点是 p 或 q,无论子树里有没有 p、q,都直接返回 node
        if (root == p || root == q) return root;
        // 如果当前结点不是 p 或 q
        if (leftTree != nullptr && rightTree != nullptr){
            // 左右子树均找到了 p、q,则当前结点 node 就是公共祖先
            return root;
        } else if (leftTree == nullptr && rightTree != nullptr){
            // 左子树没有 p、q,右子树有,直接返回右子树的 p 或 q
            return rightTree;
        } else if (leftTree != nullptr && rightTree == nullptr){
            // 左子树没有 p、q,右子树有,直接返回左子树的 p 或 q
            return leftTree;
        }
        // 以上情况均不满足,说明暂时都没有找到 p、q,返回空指针
        return nullptr;
    }
};

总结:二叉搜索树

遇到在二叉搜索树上求最值、求差值问题,其实都可以先把二叉搜索树转化为一个有序数组,在有序数组上求解问题会变得简单很多。

另一种方法是在中序遍历的过程中使用双指针法,即在递归遍历中用一个指针 cur 指向当前结点,用一个指针 pre 指向上一个遍历过的结点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2051391.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于 Konva 实现Web PPT 编辑器(一)

前言 目前Web PPT编辑比较好的库有PPTist(PPTist体验地址)&#xff0c;是基于DOM 的渲染方案&#xff0c;相比 Canvas 渲染的方案&#xff0c;在复杂场景下性能会存在一定的差距。不过确实已经很不错了&#xff0c;本应用在一些实现思路、难点攻克上也参考了pptist的思想&#…

1:html的介绍与基础1

目录 1.1html的介绍 1.2html的基础1 1.2.1标题&#xff0c;头部与基本的格式怎么写 1.2.1.1标题与基本格式 1.2.1.2头部 1.2.2段落 1.2.3链接 1.2.3.1基本的网页链接 1.2.3.2图像链接 1.2.4注释 1.1html的介绍 HTML是一种标记语言&#xff0c;用于创建&#xff0c;设…

EmguCV学习笔记 VB.Net和C# 下的OpenCv开发

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 笔者的博客网址&#xff1a;https://blog.csdn.net/uruseibest 本教程将分为VB.Net和C#两个版本分别进行发布。 教程VB.net版本请…

高效同步与处理:ADTF流服务在自动驾驶数采中的应用

目录 一、ADTF 流服务 1、流服务源&#xff08;Streaming Source&#xff09; 2、流服务汇&#xff08;Streaming Sink&#xff09; 二、数据链路 1、数据管道&#xff08;Data Pipe&#xff09; 2、子流&#xff08;Substreams&#xff09; 3、触发管道&#xff08;Tri…

遥感之常用各种指数总结大全

目前在遥感领域基本各种研究领域都会用到各种各样的指数&#xff0c;如水体指数&#xff0c;植被指数&#xff0c;农业长势指数&#xff0c;盐分指数&#xff0c;云指数&#xff0c;阴影指数&#xff0c;建筑物指数&#xff0c;水质指数&#xff0c;干旱指数等等众多。 本文对上…

Qt第十五章 动画和状态机

文章目录 动画框架动画架构动画框架类QPropertyAnimation串行动画组QSequentialAnimationGroup并行动画组QPararallelAnimationGroupQPauseAnimationQTimeLine窗口动画下坠效果抖动效果透明效果 状态机QStateQStateMachine 动画框架 动画架构 动画框架类 类名描述QAbstractAn…

字符串金额转换,字符串手机号屏蔽,身份证信息查看,敏感词替换

2135 在发票上面该写成零佰零拾零万贰仟壹佰叁拾伍元 我们用逆推法可以写成零零零贰壹叁伍->贰壹叁伍->2135 1.遍历获取到每一个数字&#xff0c;然后把大写放到数组里面&#xff0c;将数字当作索引&#xff0c;在数组里面查找大写 package stringdemo;import java.uti…

传输层安全性 ——TLS(Transport Layer Security)简介

TLS(Transport Layer Security)是一种广泛使用的安全协议,旨在确保互联网通信的隐私性和数据完整性。它是SSL(Secure Sockets Layer)的继任者,最初版本于1999年发布,最新版本是TLS 1.3。 TLS 握手为每个通信会话建立一个密码套件密码套件是一组算法,其中指定了一些细节…

如何轻松获取麒麟操作系统架构信息?

如何轻松获取麒麟操作系统架构信息&#xff1f; 一、使用uname -a命令二、用arch命令三、示例输出 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在使用麒麟操作系统&#xff08;Kylin OS&#xff09;时&#xff0c;了解系统的架构信息对于…

stm32单片机学习 - stm32 的命名规则

STM32命名规则: 以STM 32 F 103 C 8 T 6 A xxx为例:

动手学深度学习(pytorch)学习记录9-图像分类数据集之Fashion-MNIST[学习记录]

注&#xff1a;本代码在jupyter notebook上运行 封面图片来源 Fashion-MNIST是一个广泛使用的图像数据集&#xff0c;主要用于机器学习算法的基准测试&#xff0c;特别是图像分类和识别任务。Fashion-MNIST由德国的时尚科技公司Zalando旗下的研究部门提供。作为MNIST手写数字集…

Java并发类API——CompletionService

CompletionService 是 Java 中 java.util.concurrent 包的一部分&#xff0c;用于管理并发任务的执行&#xff0c;并以完成的顺序提供结果。它结合了线程池和阻塞队列的功能&#xff0c;用于提交任务并按照任务完成的顺序来检索结果&#xff0c;而不是按照任务提交的顺序。 接…

uni-app--》打造个性化壁纸预览应用平台(二)

&#x1f3d9;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名前端工程师 &#x1f304;个人主页&#xff1a;亦世凡华、 &#x1f306;系列专栏&#xff1a;uni-app &#x1f307;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&#xf…

python语言day7 函数式编程 面向对象编程

Java 函数式编程_java函数式编程-CSDN博客 25.Java函数式编程-CSDN博客 函数式编程&#xff1a; 通过调用函数send_email()&#xff0c;完成业务需求。将具体的业务需求封装成一个函数这样的一种解决问题的思想称它为函数式编程。 在java中本来没有函数的概念&#xff0c;因为…

指针详解

目录 1. 内存 2. 编址​编辑 3. 指针变量和地址 1&#xff09;取地址操作符&#xff08;&&#xff09; 2&#xff09;指针变量 3&#xff09;指针类型 4&#xff09;解引用操作符 4. 指针变量的大小 5. 指针变量类型的意义 1&#xff09;指针的解引用 6. 指针 -…

Java使用XXL-Job-Admin创建和管理调度任务的指南

文章目录 一、调度中心添加任务的基本方法二、配置文件中的任务配置三、创建并调用调度任务的客户端四、配置RestTemplate 总结 在日常开发中&#xff0c;我们经常需要处理各种定时任务&#xff0c;而XXL-Job作为一款强大的分布式任务调度平台&#xff0c;为我们提供了简单易用…

进程与线程(6)

有名管道&#xff1a; 目录 有名管道&#xff1a; 1.创建&#xff08;mkfifo&#xff09;&#xff1a; 2。打开&#xff08;open&#xff09;&#xff1a; 3.读写&#xff08;read /write&#xff09;&#xff1a; 4.关闭&#xff08;close&#xff09;&#xff1a; 5.卸…

presto高级用法(grouping、grouping sets)

目录 准备工作&#xff1a; 在hive中建表 在presto中计算 分解式 按照城市分组 统计人数 按照性别分组 统计人数 ​编辑 按照爱好分组 统计人数 ​编辑 按照城市和性别分组 统计人数 按照城市和爱好分组 统计人数 按照性别和爱好分组 统计人数 按照城市和性别还有…

【Qt开发】创建并打开子窗口(QWidget)的注意事项 禁止其他窗口点击、隐藏窗口、子窗口不退出的配置和解决方案

【Qt开发】创建并打开子窗口&#xff08;QWidget&#xff09;的注意事项 禁止其他窗口点击、隐藏窗口、子窗口不退出的配置和解决方案 文章目录 新建QWidget测试注意事项不要用多线程方式运行子窗口不要在打开子窗口后用阻塞死等不要用临时变量定义子窗口 禁止其他窗口的点击隐…

【Qt】常用控件QPushButton

常用控件QPushButton QWidget中涉及的各种属性/函数/使用方法&#xff0c;对Qt中的各种控件都是有效的。 QPushButton继承自QAbstractButton。这个类是抽象类&#xff0c;是其他按钮的父类。 QAbstractButton中和QPushButton相关性比较大的属性。 属性说明 text 按钮中的⽂本…