235. 二叉搜索树的最近公共祖先
题目描述
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
思路分析
做过二叉树:公共祖先问题的同学应该知道,利用回溯从低向上搜索,遇到一个节点的左子树有p,右子树有q,那么当前节点就是最近公共祖先.
但是本题是二叉搜索树,是有序的,其处理思路可以更简单.
只要从上到下遍历的时候,cur节点的数值在[p,q]区间中,就可说明该节点cur就是最近公共祖先了.
那么我们可以采用递归遍历(以先序遍历为例):
如图所示:p为节点3,q为节点5
可以看出直接按照指定的方向,就可以找到节点4,为最近公共祖先,而且不需要遍历整棵树,找到结果直接返回!
方法1:递归
递归三部曲:
参数和返回值:参数为递归的根节点以及两个节点p,q; 返回值为查找到的最近公共祖先
结束条件:当节点为空则结束即可.
每一层递归逻辑:如果cur->val 大于p->val和q->val,那么应该向左遍历,如果 cur->val小于p->val和q->val,则应该向右遍历. 如果在两者之间,则该节点即是最近公共祖先节点,直接返回该节点即可.
参考代码1
//递归法 主要是寻找区间 [p,q],root在这个区间之间,那么root就是公共祖先. TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if(root==NULL){ return NULL; } //左子树 if(root->val > p->val && root->val > q->val){//注意p,和q的大小是未知的,所以两个都要进行比较 TreeNode* left = lowestCommonAncestor(root->left,p,q);//遍历一条边的做法 if(left){//如果最后不为空则说明找到了,则直接进行返回 return left; } } if(root->val < p->val && root->val < q->val){ TreeNode* right = lowestCommonAncestor(root->right,p,q); if(right){ return right; } } return root;//如果root->val 即不都大于p,q;又不都小于p,q; 则说明root肯定是位于p,q之间的 }
方法2:迭代法
利用二叉搜索树的有序性来寻找即可.
参考代码2
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { while(root){ if(root->val > p->val && root->val > q->val){//在左子树 root = root->left; } else if(root->val < p->val && root->val < q->val){ //在右子树 root = root->right; }else{ // root在区间中间 return root; } } return NULL; }
701.二叉搜索树中的插入操作
题目描述
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
示例 1:
输入:root = [4,2,7,1,3], val = 5 输出:[4,2,7,1,3,5]
解释:另一个满足题目要求可以通过的树是:
思路分析
这个题虽然有多种答案,当时我们在解决时只需要 按照二叉搜索树的规则去遍历,遇到空节点插入节点即可.
方法一:递归(很野的路子)
如果要向root的左子树遍历,但是root->left为空,则直接插入节点返回即可,如果不为空则继续遍历.
同理遍历右子树.
参考代码
//递归法 void traversal(TreeNode* root,int val) { if(val < root->val) { //递归法的野路子.. if(!root->left) {//直接在本层判断左子树是否为空 root->left = new TreeNode(val); } else { traversal(root->left,val); } } if(val > root->val) { if(!root->right) {//在递归之前判断右子树是否为空.. root->right = new TreeNode(val); } else { traversal(root->right,val); } } return; } TreeNode* insertIntoBST(TreeNode* root, int val) { if(root==NULL) { root = new TreeNode(val); } traversal(root,val); return root; }
方法二:递归(不使用返回值)
插入节点,可以用俩辅助节点parent,cur节点.每次cur为空时,便是需要插入节点了,根绝parent->val和val的大小来进行插入的位置.
参考代码2
TreeNode* parent = new TreeNode(0); void traversal(TreeNode* cur,int val) { if(cur == NULL){ TreeNode* node = new TreeNode(val); if(val > parent->val){ parent->right = node; }else{ parent->left = node; } return; } parent = cur; if(val < cur->val){ traversal(cur->left,val); }else if(val > cur->val){ traversal(cur->right,val); } return; } TreeNode* insertIntoBST(TreeNode* root, int val) { if(root==NULL){ root = new TreeNode(0); } traversal(root,val); return root; }
方法三:递归
递归三要素:
- 参数和返回值:参数为递归子树的根节点以及要插入的节点值.
- 结束条件:当根节点为空时,插入节点,递归结束.
- 单层递归逻辑: 如果val < root->val,向左递归. val > root->val,向右递归.
参考代码3
TreeNode* insertIntoBST(TreeNode* root, int val) { if(root==NULL){//如果节点为空了,则直接以val创建节点并返回 return new TreeNode(val); } if(val < root->val){//向左子树寻找插入的位置. root->left = insertIntoBST(root->left,val); } if(val > root->val){//向右边子树寻找插入的位置 root->right = insertIntoBST(root->right,val); } return root; }
方法四:迭代
思路和不用返回值的递归类似:先找到要插入的位置的父节点,然后根据大小进行插入.
参考代码4
//迭代法 TreeNode* insertIntoBST(TreeNode* root, int val) { if(root==NULL){ root = new TreeNode(val); return root; } TreeNode* pre=NULL;//插入节点一般就定义两个变量,不要在想着用野套路本次判断下一次然后插入节点...太混乱了. TreeNode* cur = root; while(cur!=NULL){ pre = cur;//更新上一个节点 必须在这里进行更新,因为下面cur就发生变化了 if(val < cur->val){//这里不可以分开成两个if哦!因为一旦分开,第二个if中的cur就和第一个if中的cur不同了,很可能出错. cur = cur->left; }else if(val > cur->val){ cur = cur->right; } } //在pre上插入val节点 if(val < pre->val){ pre->left = new TreeNode(val); }else if(val > pre->val){ pre->right = new TreeNode(val); } return root; }
450. 删除二叉搜索树中的节点
题目描述
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
示例 1:
输入:root = [5,3,6,2,4,null,7], key = 3 输出:[5,4,6,2,null,null,7]
解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如上图所示。
另一个正确答案是 [5,2,6,null,4,null,7],如下图所示
示例 2:
输入: root = [5,3,6,2,4,null,7], key = 0 输出: [5,3,6,2,4,null,7]
解释: 二叉树不包含值为 0 的节点
示例 3:
输入: root = [], key = 0 输出: []
方法一:递归
递归三部曲:
- 确定参数和返回值:参数便是遍历的子树的根节点和要删除的节点值. 返回值是删除节点后子树的根节点.
- 结束条件:如果root==null,则说明没找到要删除的节点,直接返回
- 确定单层递归逻辑:
有以下三种情况:
第一种情况:没找到删除的节点,遍历到空节点直接返回.
第二种情况:找到了删除的节点,对于删除操作要分成四种情况,并且每次都要返回当前子树 的根节点给上一层
- 左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
- 删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
- 删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
- 左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
第三种情况:暂时还没有找到要删除的节点则继续进行递归寻找
左右孩子都不为空的情况有点难以理解,看下面动画:
参考代码
TreeNode* deleteNode(TreeNode* root, int key) { // 第一种情况:没找到删除的节点 ,遍历到空节点直接返回NULL if(root==NULL) { return root; } //第二种情况:找到了删除的节点,对于删除操作要分成四种情况,并且每次都要返回当前子树 的根节点给上一层.(因为删除节点后指向会变更) if(root->val == key) { if(!root->left && !root->right) { //1.当左右孩子都为空,直接删除节点,返回NULL delete root; return NULL; } else if(!root->right) {//2.当左孩子不为空,右孩子为空,删除节点左孩子补位.返回左孩子 TreeNode* temp = root->left; delete root; return temp; } else if(!root->left) { //3.当右孩子不为空,左孩子为空,删除节点右孩子补位.返回右孩子 TreeNode* temp = root->right; delete root; return temp; } else {//4.当左右孩子都不为空,则将删除节点的左子树 移到删除节点右子树的最左孩子位置上;并返回删除节点的右子树 TreeNode *cur = root->right; while(cur->left) { //寻找删除节点的最左侧节点 cur = cur->left; } cur->left = root->left;//移到删除节点右子树的最左孩子位置上 TreeNode* temp = root->right; delete root; return temp; } } //第三种情况:暂时还没有找到要删除的节点 if(key < root->val) { root->left = deleteNode(root->left,key); } else if(key > root->val) { root->right = deleteNode(root->right,key); } return root; }