代码随想录算法训练营第二十二天| 235. 二叉搜索树的最近公共祖先、701. 二叉搜索树中的插入操作、450. 删除二叉搜索树中的节点

[LeetCode] 235. 二叉搜索树的最近公共祖先

[LeetCode] 235. 二叉搜索树的最近公共祖先 文章解释

[LeetCode] 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 为不同节点且均存在于给定的二叉搜索树中。

[LeetCode] 235. 二叉搜索树的最近公共祖先

自己看到题目的第一想法

    1. 怎么和昨天的题重复了.

    2. 哦, 要利用一下二叉搜索树的特性, 有序的, 递增递减的?

    3. 快给我看答案!!!

看完代码随想录之后的想法

    1. 神奇的区间法: 如果找到一个节点, 该节点是第一个出现在 [p, q] 区间里的, 则这个节点就是公共祖先.

    2. 因为要找第一个位于 [p, q] 区间里的节点, 因此要用先序遍历. 同时当第一个位于 [p, q] 区间里的节点出现时, 有两种情况:

        2.1 要么当前节点为 p 或者 q 中的一个, 并且 p 或者 q 中的另一个位于当前节点的某个分支.

        2.2 要么 p 或者 q 中的一个位于当前节点的左子树, 另一个位于当前节点的右子树.

    3. 不管 2 中当前节点是 2.1 还是 2.2 的情况, 此时节点往左节点或者右节点移动, 移动后一定会覆盖不到之前的某个分支, 导致无法匹配到公共节点.

    4. (但是自己真的想不到这点, 还是比较沮丧的.)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
// 无脑递归法: 复习一下(没有限制 p 和 q 必须存在的解法)
// 复习过程 1 遍 AC
class Solution {
    private int matchCount = 0;
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) {
            return null;
        }
        TreeNode result = findNode(root, p, q);
        if (matchCount == 2) {
            return result;
        }
        return null;
    }

    private TreeNode findNode(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) {
            return null;
        }
        TreeNode leftNode = findNode(root.left, p, q);
        TreeNode rightNode = findNode(root.right, p, q);
        if (leftNode != null && rightNode != null) {
            return root;
        }
        if (root.val == p.val || root.val == q.val) {
            matchCount++;
            return root;
        }
        if (leftNode != null) {
            return leftNode;
        } else if (rightNode != null) {
            return rightNode;
        } else {
            return null;
        }
    }
}
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
// 递归法: 区间定位法
class Solution {
    private int matchCount = 0;
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) {
            return null;
        }
        if (root.val > p.val && root.val > q.val) {
            return lowestCommonAncestor(root.left, p, q);
        } else if (root.val < p.val && root.val < q.val) {
            return lowestCommonAncestor(root.right, p, q);
        } else {
            return root;
        }    
    }
}

自己实现过程中遇到哪些困难

    无

[LeetCode] 701. 二叉搜索树中的插入操作

[LeetCode] 701. 二叉搜索树中的插入操作 文章解释

[LeetCode] 701. 二叉搜索树中的插入操作 视频解释
题目:

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果

示例 1:

输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
解释:另一个满足题目要求可以通过的树是:

示例 2:

输入:root = [40,20,60,10,30,50,70], val = 25
输出:[40,20,60,10,30,50,70,null,null,25]

示例 3:

输入:root = [4,2,7,1,3,null,null,null,null,null,null], val = 5
输出:[4,2,7,1,3,5]

提示:

  • 树中的节点数将在 [0, 10^4]的范围内。
  • -10^8 <= Node.val <= 10^8
  • 所有值 Node.val 是 独一无二 的。
  • -10^8 <= val <= 10^8
  • 保证 val 在原始BST中不存在。

[LeetCode] 701. 二叉搜索树中的插入操作

自己看到题目的第一想法

    一开始想的很复杂, 首先要从根节点开始查找, 通过递归的方式往左或者往右递归. 然后要判断下一个节点是否大于自己, 是的话就插入到当前节点和下一个节点之间. 否则继续往下遍历. 但是这个想法是错误的. 因为当插入值位于当前节点和下一个节点之间时, 如果插在当前节点和下一个节点之间, 则无法保证下一个节点的子节点都大于当前值, 因此是错误的.

    后来在递归的过程中, 产生了比较自然的想法, 根据二叉树的性质, 从跟节点开始, 如果大于根节点就往右遍历, 小于就往左遍历. 如果遇到空, 则说明找到了待添加的位置. 同时表示当前值位于当前子树的最小值的位置. 这才是正确的.

看完代码随想录之后的想法

    和自然想法一致. 但是有一点很关键的要注意到: 利用递归的返回值完成回溯, 有时候会有一定的便利性.

// 迭代版: 减少代码行数版
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 {
            root.right = insertIntoBST(root.right, val);
        }
        return root;
    }
}
// 迭代版: 没有返回值版
class Solution {
    private TreeNode parentNode = null;
    public TreeNode insertIntoBST(TreeNode root, int val) {
        traversal(root, val);
        if (parentNode == null)  {
            return new TreeNode(val);
        }
        if (parentNode.val > val) {
            parentNode.left = new TreeNode(val);
        } else {
            parentNode.right = new TreeNode(val);
        }
        return root;
    }

    private void traversal(TreeNode root, int val) {
        if (root == null) {
            return;
        }
        parentNode = root;
        if (root.val > val) {
            traversal(root.left, val);
        } else {
            traversal(root.right, val);
        }
    }
}
// 迭代版本:
class Solution {
    private TreeNode parentNode = null;
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) {
            return new TreeNode(val);
        }
        TreeNode node = root;
        while (node != null) {
            parentNode = node;
            if (node.val > val) {
                node = node.left;
            } else {
                node = node.right;
            }
        }
        if (parentNode.val > val) {
            parentNode.left = new TreeNode(val);
        } else {
            parentNode.right = new TreeNode(val);
        }
        return root;
    }
}

自己实现过程中遇到哪些困难

    无, 实际上掌握了思想之后, 无论递归法、迭代法、无返回值版, 都变得异常的简单.

[LeetCode] 450. 删除二叉搜索树中的节点

[LeetCode] 450. 删除二叉搜索树中的节点 文章说明

[LeetCode] 450. 删除二叉搜索树中的节点 视频说明

题目:

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。

示例 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
输出: []

提示:

  • 节点数的范围 [0, 104].
  • -105 <= Node.val <= 105
  • 节点值唯一
  • root 是合法的二叉搜索树
  • -105 <= key <= 105

[LeetCode] 450. 删除二叉搜索树中的节点

自己看到题目的第一想法

    这题应该没那么难吧, 找到当前节点, 同时记录父节点.  对于搜索二叉树, 左子树需要放到右子树的最左下角的元素下.

    写了半天怎么老写不出来呢, 那么多奇怪的变量到底是干嘛的...

看完代码随想录之后的想法

    1. 原来挺简单的... 代码很清爽

    2. 这里要提一下在 ”往搜索二叉树中插入元素“ 中提到的, 利用递归函数的返回值完成回溯, 有时候会带来一定的便利性.

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
// 神奇的递归返回法!!!
class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) {
            return null;
        }
        if (root.val == key) {
            if (root.left == null && root.right == null) {
                // 元素左右为空, 说明是叶子结点. 
                // 对于上一个节点来说, 如果要删除的下一个节点是叶子结点.
                // 那么上一个节点的下一个节点就是 null
                return null;
            } else if (root.left != null && root.right == null) {
                // 左子树非空, 右子树为空
                return root.left;
            } else if (root.left == null && root.right != null) {
                return root.right;
            } else {
                TreeNode node = root.right;
                while (node.left != null) {
                    node = node.left;
                }
                node.left = root.left;
                return root.right;
            }
        } else if (root.val > key) {
            root.left = deleteNode(root.left, key);
        } else {
            root.right = deleteNode(root.right, key);
        }
        return root;
    }
}

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
// 迭代法
class Solution {
    private TreeNode preNode;
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) {
            return null;
        }
        TreeNode node = root;
        while (node != null) {
            if (node.val == key) {
                break;
            }
            preNode = node;
            if (node.val > key) {
                node = node.left;
            } else {
                node = node.right;
            }
        }
        if (preNode == null) {
            root = deleteNode(root);
        } else if (preNode.val > key) {
            preNode.left = deleteNode(preNode.left);
        } else {
            preNode.right = deleteNode(preNode.right);
        }
        return root;
    }

    private TreeNode deleteNode(TreeNode node) {
        if (node == null) {
            return null;
        }
        if (node.left == null) {
            return node.right;
        } else if (node.right == null) {
            return node.left;
        } else {
            TreeNode temp = node.right;
            while (temp.left != null) {
                temp = temp.left;
            }
            temp.left = node.left;
            return node.right;
        }
    }
}

自己实现过程中遇到哪些困难

    那可真的是太多了, 慢慢添加的变量, 一堆的变量. 最终演化成很多 case 过不了的垃圾代码.

    主要的问题在于没有理顺清楚, 要删除的节点都有哪些情况, 以及没有确定的用递归的思想去解决问题. 这都导致了思绪的杂乱. 左空右空、左非空右空、左空右非空、左右非空、找不到节点, 这五种情况列出来之后, 代码就变得非常的顺畅了.

  • 13
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值