每日算法!!

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

题目思路

利用二叉搜索树的特性去做这道题,核心思想就一句话:

如果pq在一个节点的两侧,则这个节点就是pq的最近公共祖先,如果qpcurr节点的同一侧,需要继续查找

题解代码

/*   二叉树的最近公共祖先同样可以解决这道题,但是效率不高
    求最近公共祖先,要从下向上遍历,那么就要使用后序遍历,在遍历过程中,如果我们遇到了目标节点,就向上返回。
    遍历到父节点时
    1.判断如果左右子节点同时不为null,则说明遇到了目标节点,那么自己就是最近的父节点,则向上返回自己
    2.判断如果左右子节点一个为null,一个不为null,则向上返回不为null的那个值,因为那个值就是目标节点的值
    3.判断如果左右子节点都为null,则向上返回null
    通过上述递归,就会将第一个遇到的左右子树不为null的祖先节点返回,这个节点就是最近公共祖先
 */
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    // 如果遍历到的节点为目标节点或者为null,则返回【向上回溯】
    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 root;
    }
    if (left == null && right != null) {
        return right;
    } else if (left != null && right == null) {
        return left;
    } else {
        return null;
    }
}
 
/*
     利用二叉搜索树树的特性,根节点的左子树全部小于根节点,右子树全部大于根节点
     那么,【如果p和q在一个节点的两侧,则这个节点就是p和q的最近公共祖先】
  */
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    TreeNode curr = root;
    //  q 和 p 在curr节点的同一侧,需要继续查找
    while (curr.val < p.val && curr.val < q.val || curr.val > p.val && curr.val > q.val) {
        if (curr.val < p.val) {
            curr = curr.right;
        } else {
            curr = curr.left;
        }
    }
    return curr;
}
 
/*
       迭代2,个人认为比上述迭代更好理解
    */
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    while (true) {  // 根据题意,题目中一定会有p和q的公共祖先,所以可以直接死循环,遇到符合条件的再退出即可
        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;
}

/**
 * 递归法做法,思路和上面类似
 * 当我们从上向下去递归遍历,第一次遇到 cur节点是数值在[q, p]区间中,那么cur就是 q和p的最近公共祖先。
 */
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;
}


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

题目思路

二叉搜索树的插入操作可以分为下述几个步骤:

  1. 先找到待插入节点的位置

  2. 在找待插入节点位置的过程中,需要不断记录待插入节点的父节点prev

  3. 当找到待插入节点的位置时,将valprev.val进行比较,判断该节点应该插入到父节点的左孩子还是右孩子

题解代码

/*
    迭代法,先找到待插入节点的位置,然后记录待插入节点的父节点,最后通过于父节点的判断觉得插入父节点的左边还是右边
 */
public TreeNode insertIntoBST(TreeNode root, int val) {
    if (root == null) {
        return new TreeNode(val);
    }
    // 寻找插入位置
    TreeNode curr = root;
    TreeNode prev = null; // 记录父节点,如果最后遍历到合适的插入位置,需要根据父节点插入父节点的左孩子或者右孩子
    while (curr != null) {
        if (curr.val > val) {
            prev = curr;
            curr = curr.left;
        } else if (curr.val < val) {
            prev = curr;
            curr = curr.right;
        }
    }
    // 如果父节点的值大于待插入元素的值,则插入到父节点的左边,反之插入右边
    if (prev.val > val) {
        prev.left = new TreeNode(val);
    } else {
        prev.right = new TreeNode(val);
    }
    return root;
}

 

// 递归法
// 参数:二叉树根节点和待插入数值
// 返回值:插入的节点
public TreeNode insertIntoBST(TreeNode root, int val) {
    // 终止条件:如果遍历到null,说明找到了插入位置,返回插入的节点即可
    if (root == null) { // 如果当前节点为空,也就意味着val找到了合适的位置,此时创建节点直接返回。
        return new TreeNode(val);
    }
    // 单层递归的逻辑
    if (root.val < val) {
        root.right = insertIntoBST(root.right, val); // 递归创建右子树
    } else if (root.val > val) {
        root.left = insertIntoBST(root.left, val); // 递归创建左子树
    }
    return root;

}

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

题目思路

删除节点

要删除某节点(称为 D),必须先找到被删除节点的父节点,这里称为 Parent

  1. 删除节点没有左孩子,将右孩子托孤给 Parent
  2. 删除节点没有右孩子,将左孩子托孤给 Parent
  3. 删除节点左右孩子都没有,已经被涵盖在情况1、情况2 当中,把 null 托孤给 Parent
  4. 删除节点左右孩子都有,可以将它的后继节点(称为 S)托孤给 Parent,设 S 的父亲为 SP,又分两种情况
    1. SP 就是被删除节点,此时 D 与 S 紧邻,只需将 S 托孤给 Parent
    2. SP 不是被删除节点,此时 D 与 S 不相邻,此时需要将 S 的后代托孤给 SP,再将 S 托孤给 Parent

题解代码

 
public static TreeNode deleteNode(TreeNode root, int key) {
    TreeNode cur = root, curParent = null;
    // 寻找待删除节点
    while (cur != null && cur.val != key) {
        curParent = cur; // 记录父节点
        if (cur.val > key) {
            cur = cur.left;
        } else {
            cur = cur.right;
        }
    }
    // 此时的cur就是待删除节点,如果cur==null,说明没有找到待删除节点,返回原根节点root
    if (cur == null) {
        return root;
    }
    // 待删除节点为根节点,直接将待删除节点置为null
    if (cur.left == null && cur.right == null) {
        cur = null;
    } else if (cur.right == null) { // 待删除节点只有左孩子,直接将待删除节点置为其左孩子,表示删除了自身
        cur = cur.left;
    } else if (cur.left == null) { // 同上
        cur = cur.right;
    } else { // 待删除节点既有左孩子又有右孩子
        TreeNode s = cur.right; // 定义后继节点,和后继节点的父节点
        TreeNode sp = cur; 
        // 找待删除节点的后继节点
        while (s.left != null) {
            sp = s;
            s = s.left;
        }
        // 如果后继节点的父节点==待删除节点,说明后继节点和待删除节点相邻,先直接删除后继节点,后序将后继节点替换待删除节点
        if (sp.val == cur.val) {
            sp.right = s.right;
        } else { // 如果不相邻,则将后继节点父节点的左孩子赋值为后继节点的右孩子
            // 本质上还是要删除后继节点,因为如果不相邻的话,后继节点一定是其父节点的左孩子,而后继节点也只有右孩子
            sp.left = s.right;
        }
        // 删除 后继节点后,将后继节点的左右孩子都赋值为待删除节点的左右孩子
        s.right = cur.right;
        s.left = cur.left;
        // 后继节点替换待删除节点
        cur = s;
    }
    // 如果待删除节点父节点为null,说明删除的是根节点,则返回新的根节点就是cur
    if (curParent == null) {
        return cur;
    } else { // 待删除节点为父节点的左孩子,则将删除后的cur节点赋值给父节点的左孩子
        if (curParent.left != null && curParent.left.val == key) {
            curParent.left = cur;
        } else { //反之赋值给右孩子
            curParent.right = cur;
        }
        return root;
    }
}

 

/**
 * 递归法
 * 参数:二叉搜索树根节点,待删除节点值key
 * 返回值:删除后的根节点
 */
public static TreeNode deleteNode(TreeNode root, int key) {
    // 递归的终止条件
    if (root == null) {
        return null;
    }
    // 单层递归的逻辑
    if (root.val == key) { // 需要删除root
        if (root.left == null) { // 情况1,2:待删除节点没有孩子或者只有右孩子
            return root.right;
        } else if (root.right == null) {// 情况3:待删除节点只有右孩子
            return root.left;
        } else { // 情况4:待删除节点既有左孩子又有右孩子
            TreeNode s = root.right; // 后继节点
            while (s.left != null) {
                s = s.left;
            }
            s.left = root.left; // 将待删除节点的左孩子挂到后继节点的左孩子上
            root = root.right; // 删除待删除节点
            return root;
        }
    }
    root.left = deleteNode(root.left, key);
    root.right = deleteNode(root.right, key);
    return root;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值