235.二叉搜索树的最近公共祖先
视频讲解:https://www.bilibili.com/video/BV1Zt4y1F7ww
思路:
-
怎么利用二叉搜索树的特性?
- 因为二叉搜索树中,左子树总是小于根节点,右子树总是大于根节点。所以这里就可以知道我们只需要遍历局部节点就可以,而不需要向二叉树的公共祖先那样遍历整个二叉树,因为搜索树的特性已经可以帮我们准确定位公共祖先所在的位置
-
如果确定二叉搜索树的公共祖先呢?
- 我们已经知道了公共祖先的定义,然后利用搜索树的特性,我们只要遇到第一个当前节点在[q,p]的区间内,那么这个节点就一定是公共祖先
-
为什么第一个遇到在[q,p]区间内的就一定是最近的公共祖先?(注意:[q,p]并没说谁大谁小,只是举例,做题还得判断)
- 如图,我们可以看到qp的公共祖先是2,并且2也是我们遇到的第一个在区间内的节点。
- 假如我们继续向下遍历,如果向左遍历,那么p节点就会被我们错过,2的左孩子中就不可能在找的到p节点
- 同理向右遍历,就会错过q节点,2的右孩子中也不可能在找的到q节点
- 所以我们遇到的第一个在[q,p]区间范围内的节点,一定是最近的公共祖先
-
遍历顺序?
- 这题就不需要我们自底向上遍历,而是从上向下遍历。因为利用特性可以直接定位到公共祖先到底是在左子树还是右子树,这题也没有前中后遍历
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) return null;
// 利用二叉搜索树的特性
// 如果当前节点大于pq节点,就向左遍历
// 此时公共祖先一定是在当前节点的左子树中的
// q,p都要判断,因为并没有说q和p谁大谁小
if(root.val>p.val && root.val>q.val){
TreeNode left = lowestCommonAncestor(root.left,p,q);
// 此时如果找到公共祖先可以直接返回,不用继续遍历
return left;
}
if(root.val<p.val && root.val<q.val){
TreeNode right = lowestCommonAncestor(root.right,p,q);
return right;
}
return root;
}
}
701.二叉搜索树中的插入操作
视频讲解:https://www.bilibili.com/video/BV1Et4y1c78Y
思路:本题说可以选择是否重构二叉树,就是说存在多种插入方式,只要最后还是二叉搜索树即可,那么我们只要保持一直向叶子节点插入数据,这题就比较方便了
// 递归法
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
// 如果遍历到叶子节点就创建节点
if(root == null) 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;
}
}
// 迭代法
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if(root == null) return new TreeNode(val);
TreeNode newRoot = root; // 记录头节点
TreeNode per = root; // 记录叶子节点的父节点
// 找到待插入位置的父节点
while(root!=null){
per = root;
if(val < root.val)
root = root.left;
else if(val > root.val)
root = root.right;
}
// 判断是向左还是右插入
if(val > per.val){
per.right = new TreeNode(val);
}else{
per.left = new TreeNode(val);
}
return newRoot;
}
}
450.删除二叉搜索树中的节点
视频讲解:https://www.bilibili.com/video/BV1tP41177us
删除的逻辑:
- 没有找到节点
- 删除的节点为叶子节点,那么就直接返回null,再连接给父节点
- 删除的节点左孩子为空,右孩子不为空,那么就直接返回删除节点的右孩子
- 删除的节点左孩子不为空,右孩子为空,那么就直接返回删除节点的左孩子
- 删除节点的左右孩子都不为空,(因为存在多种重构二叉树的方式,所以我们只选择一种即可)
- 可以把删除节点的左孩子节点,移动到右孩子节点中只比删除节点大一点的节点
- 如图:删除节点2时,我们应该把节点0移动到2的右孩子中只比2大一点的节点下面,也就是节点3的左孩子
- 所以怎么找到这个大一点的值?
- 找大一点的值,就是从删除节点(2)的右节点(4)开始,然后从右节点(4)开始一直向左节点遍历直到当前节点的左节点为null,那么当前节点就是比删除节点大一点的值
- (注:大一点,不是表示值比2大一点,而是比2大的第一个数,即2-4-7中的4也算)
- 可以把删除节点的左孩子节点,移动到右孩子节点中只比删除节点大一点的节点
最后,每次的返回值表示:删除节点后的新节点
class Solution {
TreeNode per;
//
public TreeNode deleteNode(TreeNode root, int key) {
if(root == null) return null;
// 找到的情况
if(key == root.val){
// 情况2,这里每次的返回值,都会在下方接收到
if(root.left==null && root.right==null) return null;
// 情况3
else if(root.left!=null && root.right==null) return root.left;
// 情况4
else if(root.left==null && root.right!=null) return root.right;
// 情况5
else {
// 记录删除节点的右节点
TreeNode cur = root.right;
// 开始向左遍历,找到只比删除节点大一点的值
while(cur.left!=null) cur = cur.left;
// 找到后,将删除节点的左孩子整体移动到,当前节点的左孩子
cur.left = root.left;
// 最后将移动好的右孩子返回
return root.right;
}
}
// 查找当前节点中的右孩存不存在待删除的节点
// 将删除后的新的右孩子,返回给父节点接收
// 相当于更新当前节点的右孩子
if(key > root.val) root.right = deleteNode(root.right,key);
if(key < root.val) root.left = deleteNode(root.left,key);
return root;
}
}