剑指 Offer 36. 二叉搜索树习题

在这里插入图片描述

分享两道比较接近的题目,同样都是用二叉搜索树来构建一个双向循环链表,只不过后一道题是前一道题的变型题

在这里插入图片描述
二叉搜索树与双向链表.

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/

/*
 
    解题思路:
    这一道题的做法比较简单,实现思路也比较简单,做法是我们可以定义一个前驱指针记录cur的上一个结点,
    我们可以根据搜索树的性质是有序的为出发点,递归到二叉树的最左边那么这个结点就是整颗树中的最小值了(val值为4),
    但是此时的prev确是nullptr,那么就只让cur的前驱指针left指向prev,再更新prev的位置将cur赋值给prev,再以cur为根
    结点去递归右子树,但是右子树为nullptr那么就返回,回退到调用的地方,而上一层的调用处是val值为6的结点,这个时候prev
    并不为nullptr,那么就将prev->right = cur; cur->left = prev; 链接两个结点的关系,重复此过程,只到root的左子树遍历完了
    右子树也遍历完了那么就返回,那么整个双向链表就构建完了,最后回到调用处只需要以root出发寻找左子树的最左结点它就是链表的头节点
    
 */
class Solution {
public:
    void inorder(TreeNode* cur, TreeNode*& prev) //通过引用能够改变prev的位置
    {
        if(!cur) return ;  //为nullptr就返回
        inorder(cur->left, prev); //递归到二叉树的左子树
        if(prev) prev->right = cur; //prev的后继指针指向cur
        cur->left = prev;        //cur的前驱指针指向prev  
                                 //总结:以上这种做法是为了构建两个结点直接的联系
        prev = cur;  //更新prev的位置
        inorder(cur->right, prev); //递归右子树
    }
    
    TreeNode* Convert(TreeNode* pRootOfTree) {
        if(!pRootOfTree) return pRootOfTree;  //oj特殊判断
        TreeNode* prev = nullptr;    
        inorder(pRootOfTree, prev); //递归构建双向循环链表
        while(pRootOfTree->left)  //寻找双向链表的头节点
        {
            pRootOfTree = pRootOfTree->left; 
        }
        return pRootOfTree;
    }
};

在这里插入图片描述

在这里插入图片描述
二叉搜索树与双向链表.

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;

    Node() {}

    Node(int _val) {
        val = _val;
        left = NULL;
        right = NULL;
    }

    Node(int _val, Node* _left, Node* _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/

/*
    解题思路:
    想要构建一个双向循环链表就必须即找到头节点,又找到尾结点, 最后让头的前驱指针指向尾,尾的后继指
    指向头,而重要的是如何记录头尾指针,头指针比较好找,根据题意我们需要建立一个有序的双向链表,而
    搜索树的性质就已经决定了,树的最左左子树最左就是整个树的最小值,那么就用他来做头节点,找尾结点
    可以在构建双向链表的时候记录下来这里用prev记录,当整个树遍历完了prev也就确定了尾节点的位置


    链表构建过程:目标 1、建立升序双向链表  2、找出头尾结点

    逻辑设计:
        1、假设头尾结点都是null,那么cur遍历到最左侧的时候就将cur的值赋值给head,那么头节点就确
        定好了,这个时候prev的值还是null,不需要处理, 直接将cur的left指向prev,再去递归右子树(右子为
        null直接返回,)这时候头结点(head)的位置就已经确定好了,

        2、当右子树递归完了,那么结点值为1的这颗子树就遍历完了,递归结束返回上一层位置是val值为2的结点
        处,那么此事的cur就是2,而prev就是cur的上一个结点val值为1的结点。
        prev->righ = cur;  cur->left = prev;  就将两个结点的关系链接起来了,紧接着又去递归右子树  
        将cur的值赋值给prev, 紧接着递归右子树cur到val值为3的结点,再将两个结点的关系链接,如此反
        复(中间过程粗略省去,读者可以自行画图思考,逻辑跟上面描述的是一样的)    
        直接prev走到val值为5的结点,递归右子树完了后整个树就遍历完了, 那么就返回,最终prev的位置
        就确定好了,最后首位相连返回head即可
*/
class Solution {
public:
    Node* prev = nullptr, *head = nullptr; //记录
    void inorder(Node *cur)
    {
        if(!cur) return ;  //判空处理
        inorder(cur->left); //递归左子树
        if(prev) prev->right = cur;  //cur的上一个结点(prev)的后继指针指向cur
        else head = cur; //记录头节点
        cur->left = prev; //cur的前驱指针指向prev
        prev = cur;    //更新prev的位置

        inorder(cur->right); //递归右子树
    }

    Node* treeToDoublyList(Node* root) {
        if(!root) return nullptr;  //oj的特殊判空处理
        inorder(root);  //走搜索树的中序遍历
        head->left = prev;  //将已经记录好的prev和head首位相连
        prev->right = head;
        return head;  //返回头节点
    }
};

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
二叉树中和为某一值的路径.


/*
    解题思路:
    可以通过从根开始递归左右子树两边,将该路径上所有存放的值都push_back进一个容器,这样一来遍历的过程中相当于一存放路径一
    边计算该结果是sum的值(当然这里使用的是减法规则,拿sum的值减去一条路径的所有值,如果sum可以被置零并且当前结点的是树的叶子
    那么该路径就是所有结点的和值就是sum的结果,最后将path对象push_back进ans容器就行,直到整颗树遍历完了就返回ans存放的路径
 */
 
class Solution {
public:
    vector<vector<int>> ans;  //存放路径
    vector<int> path;		//存放路径

    void dfs(TreeNode* cur, int target)
    {
        if(!cur) return ;  //递归出口条件
        path.push_back(cur->val);	
        //走二叉树的前序遍历将每一个子树的根的值存放进容器,这就是存放他的路径
        target -= cur->val;  //这里从sum直接减到0去判断更方便,就不需要再额外定义一个变量了
        if(!cur->left && !cur->right && !target) ans.push_back(path);  
        
        
        dfs(cur->left, target);    //递归左子树
        dfs(cur->right, target);  //递归右子树

        path.pop_back();  //回溯清空容器
    }

    vector<vector<int>> pathSum(TreeNode* root, int target) {
        if(!root) return ans;   //oj特殊判断
        dfs(root, target);
        return ans;
    }
};

在这里插入图片描述
二叉搜索树的第k大节点.

这道题的解题思路非常简单,因为搜索二叉树的中序是有序的,所以在遍历过程中只需要从右子树的最右开始倒序遍历k个结点,最后出现的位置就是第k大结点

class Solution {
public:
    void inorder(TreeNode* cur, int &ans ,int& k)
    {
        if(!cur) return ;
        inorder(cur->right, ans, k) ;
        
        if(k == 0) return ;   //当k的值已经为0了就可以返会
        if(k--) ans = cur->val; //当k值为1的时候存放的就是第k大的结点值
      
        inorder(cur->left, ans, k);
    }
    int kthLargest(TreeNode* root, int k) {
        int ans = 0;
        inorder(root, ans, k);
        return ans;
    }
};

二叉树进阶面试题

在这里插入图片描述
重建二叉树.

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* _buildTree(vector<int>& pre, int &pi, vector<int>& inor, int inbegin, int inend)
    {
        if(inbegin > inend) return nullptr;
        TreeNode* root = new TreeNode(pre[pi]); //利用前序确定根的位置
        pi++;
        //通过确定根的位置,在利用中序遍历确定根的左子树和右子树
        int rooti = inbegin; 
        while(rooti <= inend)
        {   
            if(inor[rooti] == root->val) break;  //找出rooti在中序中的位置
            rooti++; //通过根确定树的左右子树
        }
        //创建root的左子树
        root->left = _buildTree(pre, pi, inor, inbegin, rooti - 1);
        //创建root的右子树
        root->right = _buildTree(pre, pi, inor, rooti + 1, inend);
        return root; //返回根结点
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int pi = 0;
        return _buildTree(preorder, pi, inorder, 0, inorder.size() - 1);
    }
};

在这里插入图片描述
从中序与后序遍历序列构造二叉树.

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    /*
        这道题与从前序与中序遍历序列构造二叉树的思路是类似的只不过遍历的顺序不一样
        前序遍历:根-》左-》右
        中序遍历:左-》根-》右
        所以可以考虑倒着遍历postorder,先创建右子树再创建左子树
    */
    TreeNode* _buildTree(vector<int>& postorder, int& pi, vector<int>& inorder
    , int inbegin, int inend)
    {
        //区间不存在
        if(inbegin > inend) return nullptr;
        //通过后序(postorder)创建根结点
        TreeNode* root = new TreeNode(postorder[pi--]);
        //通过根值确定中序(inorder)遍历时根的左右子树
        int rooti = inbegin;
        while(rooti <= inend)
        {   
            if(inorder[rooti] == root->val) break; //确定根结点在中序中的位置
            rooti++;
        }
        //创建右子树
        root->right = _buildTree(postorder, pi, inorder, rooti + 1, inend);
        //创建左子树
        root->left = _buildTree(postorder, pi, inorder, inbegin, rooti - 1);
        //返回root
        return root;
    }

    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int pi = postorder.size() - 1;  //倒序遍历
        return  _buildTree(postorder, pi, inorder, 0, inorder.size() - 1);
    }
};

在这里插入图片描述

二叉搜索树的后序遍历序列.

//确定根的位置,通过根的值划分两段区间的值,【0,K - 1】区间的值比root小,【k + 1, R】区间的值比根大
 //对这两段区间的值通过大小判断确定是不是搜索树,如果不是则return false,否则继续递归
 
class Solution {
public:
    bool dfs(vector<int>& postorder, int L , int R)
    {
        if(L > R) return true; //
        int k = L;
        while(k < R && postorder[k] < postorder[R]) k++; //将值小于root的值给过滤掉
        for(;k < R; k++){  						//判断剩余的值如果比根结点小那么就不是搜索树
            if(postorder[k] < postorder[R]) return false;
        }

        return dfs(postorder, L, k - 1) 
        	&& dfs(postorder, k + 1, R);  //递归左右子树  
    }

    bool verifyPostorder(vector<int>& postorder) {
        if(postorder.empty()) return true;
        return dfs(postorder, 0, postorder.size() - 1);
    }
};
  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱生活,爱代码

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

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

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

打赏作者

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

抵扣说明:

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

余额充值