代码随想录算法训练营(JAVA)| 第六章 二叉树part08

今日任务

力扣 236. 二叉树的最近公共祖先235. 二叉搜索树的最近公共祖先701. 二叉搜索树中的插入操作450. 删除二叉搜索树中的节点

题目 :236. 二叉树的最近公共祖先

思路

情况1:

如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。

情况2:

二叉树节点数值是不重复的,而且一定存在 q 和 p但是很多人容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q(p)。

其实情况一 和 情况二 代码实现过程都是一样的,也可以说,实现情况一的逻辑,顺便包含了情况二。

因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本身就是 公共祖先的情况。


递归三部曲:
  • 确定递归函数返回值以及参数

需要递归函数返回值,来告诉我们是否找到节点q或者p,那么返回值为bool类型就可以了。

但我们还要返回最近公共节点,可以利用上题目中返回值是TreeNode * ,那么如果遇到p或者q,就把q或者p返回,返回值不为空,就说明找到了q或者p。

  • 确定终止条件

遇到空的话,因为树都是空了,所以返回空。

那么我们来说一说,如果 root == q,或者 root == p,说明找到 q p ,则将其返回,这个返回值,后面在中节点的处理过程中会用到,那么中节点的处理逻辑,下面讲解。

  • 确定单层递归逻辑

值得注意的是 本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。

拓展

如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树呢?

搜索一条边的写法:

复制代码
if (递归函数(root->left)) return ;

if (递归函数(root->right)) return ;

搜索整个树写法:

复制代码
left = 递归函数(root->left);  // 左
right = 递归函数(root->right); // 右
left与right的逻辑处理;         // 中 

看出区别了没?

在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)



关于回溯

那么我给大家归纳如下三点

  1. 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。

  2. 在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。

  3. 要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。


题解

java 复制代码
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) return root;

        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);

        if (left == null && right == null) 
        {
            return null;
        } else if (left == null && right != null) 
        {
            return right;
        } else if (left != null && right == null) 
        {
            return left;
        } else 
        {
            return root;
        }
    }
}

题目 :235. 二叉搜索树的最近公共祖先

思路

本题是二叉搜索树,二叉搜索树是有序的,要好好利用这一点(区间思想),卡哥的代码太妙了!

只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是p 和 q的公共祖先。 那问题来了,一定是最近公共祖先吗

第一次遇到 cur节点是数值在[q, p]区间中,那么cur就是 q和p的最近公共祖先。

因为,第一次遇到 cur节点是数值在[q, p]区间中,即 节点5,此时可以说明 q 和 p 一定分别存在于 节点 5的左子树,和右子树中。

此时节点5是不是最近公共祖先? 如果 从节点5继续向左遍历,那么将错过成为p的祖先, 如果从节点5继续向右遍历则错过成为q的祖先。

递归三部曲如下:

  • 确定递归函数返回值以及参数

参数就是当前节点,以及两个结点 p、q。

返回值是要返回最近公共祖先,所以是TreeNode * 。

复制代码
TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q)
  • 确定终止条件

遇到空返回就可以了,代码如下:

复制代码
if (cur == NULL) return cur;

其实都不需要这个终止条件,因为题目中说了p、q 为不同节点且均存在于给定的二叉搜索树中。也就是说一定会找到公共祖先的,所以并不存在遇到空的情况。

  • 确定单层递归的逻辑

在遍历二叉搜索树的时候就是寻找区间[p->val, q->val](注意这里是左闭又闭)

那么如果 cur->val 大于 p->val,同时 cur->val 大于q->val,那么就应该向左遍历(说明目标区间在左子树上)。

需要注意的是此时不知道p和q谁大,所以两个都要判断


题解

java 复制代码
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
        if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
        return root;
    }
}
java 复制代码
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while (true) 
        {
            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 
            {
                break;
            }
        }
        return root;
    }
}

题目 :701. 二叉搜索树中的插入操作

思路

递归三部曲:
  • 确定递归函数参数以及返回值

参数就是根节点指针,以及要插入元素,这里递归函数要不要有返回值呢?

可以有,也可以没有,但递归函数如果没有返回值的话,实现是比较麻烦的。

有返回值的话,可以利用返回值完成新加入的节点与其父节点的赋值操作

递归函数的返回类型为节点类型TreeNode * 。

  • 确定终止条件

终止条件就是找到遍历的节点为null的时候,就是要插入节点的位置了,并把插入的节点返回。

这里把添加的节点返回给上一层,就完成了父子节点的赋值操作了,详细再往下看。

  • 确定单层递归的逻辑

此时要明确,需要遍历整棵树么?

别忘了这是搜索树,遍历整棵搜索树简直是对搜索树的侮辱。

搜索树是有方向了,可以根据插入元素的数值,决定递归方向。

复制代码
if (root->val > val) root->left = insertIntoBST(root->left, val);
if (root->val < val) root->right = insertIntoBST(root->right, val);
return root;

到这里,大家应该能感受到,如何通过递归函数返回值完成了新加入节点的父子关系赋值操作了,下一层将加入节点返回,本层用root->left或者root->right将其接住

题解

递归
java 复制代码
class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) return new TreeNode(val);

        if (root.val > val)
        {
            root.left = insertIntoBST(root.left, val);
        } else if (root.val < val) {
            root.right = insertIntoBST(root.right, val);
        }
        return root;
    }
}
迭代
java 复制代码
class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) return new TreeNode(val);
        TreeNode pre = root;
        TreeNode newRoot = root;
        while (root != null) 
        {
            pre = root;
            if (root.val > val) 
            {
                root = root.left;
            } else if (root.val < val)
            {
                root = root.right;
            }
        }

        if (pre.val > val)
        {
            pre.left = new TreeNode(val);
        } else 
        {
            pre.right = new TreeNode(val);
        }
        return newRoot;
    }
}

题目 :450. 删除二叉搜索树中的节点

思路

删除的话,特别需要注意删除的节点有左右子孩子

递归三部曲:
  • 确定递归函数参数以及返回值

说到递归函数的返回值,这里也可以通过递归返回值删除节点。

  • 确定终止条件

遇到空返回,其实这也说明没找到删除的节点,遍历到空节点直接返回了

  • 确定单层递归的逻辑

有以下五种情况:

  • 第一种情况:没找到删除的节点,遍历到空节点直接返回了
  • 找到删除的节点
    • 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
    • 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
    • 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
    • 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。

题解

递归
java 复制代码
class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) return root;
        if (root.val == key) 
        {
            if (root.left == null) 
            {
                return root.right;
            } else if (root.right == null)
            {
                return root.left;
            } else 
            {
                TreeNode cur = root.right;
                while (cur.left != null) {
                    cur = cur.left;
                }
                cur.left = root.left;
                root = root.right;
                return root;
            }
        }
        if (root.val > key) root.left = deleteNode(root.left, key);
        if (root.val < key) root.right = deleteNode(root.right, key);
        return root;
    }
}
迭代
java 复制代码
class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) 
        {
            return null;
        }

        TreeNode cur = root;
        TreeNode pre = null;
        while (cur != null) 
        {
            if (cur.val > key)
            {
                pre = cur;
                cur = cur.left;
            } else if (cur.val < key)
            {
                pre = cur;
                cur = cur.right; 
            }else
            {
                break;
            }
        }
        if (pre == null)
        {
            return deleteOneNode(cur);
        }
        if (pre.left != null && pre.left.val == key)
        {
            pre.left = deleteOneNode(cur);
        }
        if (pre.right != null && pre.right.val == key)
        {
            pre.right = deleteOneNode(cur);
        }
        return root;
    }

    public TreeNode deleteOneNode(TreeNode node) {
        if (node == null) return null;
        if (node.right == null) 
        {
            return node.left;
        }
        TreeNode cur = node.right;
        while (cur.left != null)
        {
            cur = cur.left;
        }
        cur.left = node.left;
        return node.right;
    }
}
相关推荐
风中月隐3 分钟前
C语言中以坐标的方式图解“字母金字塔”的绘制
c语言·开发语言·算法·字母金子塔·坐标图解法
q_30238195564 分钟前
告别“笨重”检测!VA-YOLO算法让疲劳驾驶识别更轻更快更准
算法·yolo
LSL666_5 分钟前
12 MyBatis的连接池
java·服务器·mybatis
Arva .7 分钟前
说说线程的生命周期和状态
java·开发语言
45288655上山打老虎8 分钟前
List容器
数据结构·windows·list
tryxr15 分钟前
HashTable、HashMap、ConcurrentHashMap 之间的区别
java·开发语言·hash
松涛和鸣16 分钟前
DAY32 Linux Thread Programming
linux·运维·数据库·算法·list
无事好时节20 分钟前
Linux 线程
java·开发语言·rpc
LYFlied20 分钟前
【每日算法】LeetCode 234. 回文链表详解
算法·leetcode·链表
我家领养了个白胖胖24 分钟前
Prompt、格式化输出、持久化ChatMemory
java·后端·ai编程