【LeetCode】450.删除二叉搜索树中的节点

题目描述

LeetCode题目链接:删除二叉搜索树中的节点

这里是引用
在这里插入图片描述
在这里插入图片描述

题目描述,被删除的节点可以用其前驱或后继顶替,这里使用后继。

迭代求解

相较于递归求解,迭代版很简单,但是精简优化过程非常容易踩坑,比较麻烦。

本题就两个要拿捏的点:

  • 二叉搜索树后继的没有左分支
  • 从上往下找,从下往上改

过程版

class Solution {
	/**
     * 被删除的节点 分情况考虑
     * <ol>
     *     <li> 只有右孩子 </li>
     *     <li> 只有左孩子 </li>
     *     <li> 叶子节点 </li>
     *     <li> 有两个孩子 </li>
     * </ol>
     *
     * @param root
     * @param key
     * @return
     */
    public TreeNode deleteNode(TreeNode root, int key) {
        
        if (root == null) return root;

        TreeNode delTag = root;
        TreeNode delParent = null; // null 时 需要单独处理

        while (delTag != null) {
            if (key < delTag.val) {
                delParent = delTag;
                delTag = delTag.left;
            } else if (key > delTag.val) {
                delParent = delTag;
                delTag = delTag.right;
            } else break;
        }

        if (delTag == null) return root;

        // 只有一个孩子的情况
        if (delTag.left == null) {
            if (delParent == null) {
                root = delTag.right;
            } else if (delParent.left == delTag) {
                delParent.left = delTag.right;
            } else {
                delParent.right = delTag.right;
            }
            return root;
        } else if (delTag.right == null) {
            if (delParent == null) {
                root = delTag.left;
            } else if (delParent.left == delTag) {
                delParent.left = delTag.left;
            } else {
                delParent.right = delTag.left;
            }
            return root;
        } else {
            // 找后继、后继的父节点
            TreeNode suc = delTag.right;
            TreeNode sucParent = delTag;

            while (suc.left != null) {
                sucParent = suc;
                suc = suc.left;
            }

            // 相邻的后继 直接顶上去
            /*
              |  			|
              |           delP
              |         /      \
              |       del       .
              |      /   \
              |     .    suc
              |         /   \
              |      null    .
              |
             */
            if (sucParent == delTag) {
                if (delParent == null) {
                    root = suc;
                } else if (delParent.left == delTag) {
                    delParent.left = suc;
                } else {
                    delParent.right = suc;
                }
                suc.left = delTag.left;
                return root;
            }
             // 子树中 删除 后继节点(无左) -> 直接将后继的右支上顶
            /*
              |				|
              |           delP
              |         /      \
              |        del      .
              |      /    \
              |     .      .
              |           / \
              |          .   .
              |         /
              |       suc
              |      /   \
              |    null   .
              |			 / \
              |         .   .
              |
             */
            else {
                if (sucParent.left == suc) {
                    sucParent.left = suc.right;
                } else {
                    sucParent.right = suc.right;
                }
                // ---------- ----------
                // 显然,从此处开始,也可以用一种不讲究的做法 将 del目标节点的值用 后继替换
                // delTag.val = suc.val; 
                // return root;
                // ---------- ----------
                
                suc.right = delTag.right;
                suc.left = delTag.left;
                
                if (delParent == null){
                    root = suc;
                }else if (delParent.left == delTag) {
                    delParent.left = suc;
                } else {
                    delParent.right = suc;
                }
                
                return root;
            }
        }
    }
}

精简优化版

在上述代码的基础上,只要意识到以下两个特点,就可以继续优化。

  • 在二叉搜索树中,节点A与其后继B 不相邻 <==> B是其父节点(bParent)的左孩子
  • 在二叉搜索树中,节点A与其后继B 相邻 <==> B一定是A的右孩子
class Solution {
	/**
     * 被删除的节点 分情况考虑
     * <ol>
     *     <li> 只有右孩子 </li>
     *     <li> 只有左孩子 </li>
     *     <li> 叶子节点 </li>
     *     <li> 有两个孩子 </li>
     * </ol>
     *
     * @param root
     * @param key
     * @return
     */
    public TreeNode deleteNode(TreeNode root, int key) {
        TreeNode delTag = root;
        TreeNode delParent = null;

        while (delTag != null) {
            if (key < delTag.val) {
                delParent = delTag;
                delTag = delTag.left;
            } else if (key > delTag.val) {
                delParent = delTag;
                delTag = delTag.right;
            } else break;
        }

        if (delTag == null) return root;

        if (delTag.left == null) {
            if (delTag.right == null) {
                delTag = null;
            } else {
                delTag = delTag.right;
            }
        } else if (delTag.right == null) {
            delTag = delTag.left;
        } else {
            // delTag 的后继
            TreeNode suc = delTag.right;
            TreeNode sucParent = delTag;
            while (suc.left != null) {  // 1、后继一定无左半支
                sucParent = suc;
                suc = suc.left;         // 2、除了相邻情况,后继节点一定是其父节点的左孩子
            }
            // 后继与相邻del <==> 后继一定在del右
            if (sucParent == delTag) {
                sucParent.right = suc.right;
            }
            // delTag 与其后继不相邻 <==> del后继节点是其父节点的左孩子
            else {
                sucParent.left = suc.right;
            }

            suc.right = delTag.right;
            suc.left = delTag.left;
            delTag = suc;
        }

        if (delParent == null) {
            return delTag;
        } else {
            if (delParent.left != null && delParent.left.val == key) {
                delParent.left = delTag;
            } else {
                delParent.right = delTag;
            }
            return root;
        }
    }
}

递归求解

public class Solution {

    /**
     *  被删除的节点 分情况考虑
     *  <ol>
     *      <li> 只有右孩子</li>
     *      <li> 只有左孩子</li>
     *      <li> 叶子节点</li>
     *      <li> 有两个孩子</li>
     *  </ol>
     * @param root
     * @param key
     * @return
     */
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root==null) return null;
        return doDelete(root, key);
    }

    /**
     * 递归解决思路:
     * <ol>
     *     <li> 当前层 node 不是目标节点,继续递归 </li>
     *     <li> 当前层 node 是目标节点
     *     <ul>
     *         <li> 只有一个孩子 直接 顶上来 </li>
     *         <li> 右两个孩子 找到后继 考虑相邻不相邻情况 </li>
     *     </ul>
     *     </li>
     * </ol>
     * <br></p><br>
     *  ---------- ---------- ----------
     *  |        .
     *  |        |
     *  |       del
     *  |      /   \
     *  |     .    suc
     *  |            \
     *  |             .
     *  ---------- ---------- ----------
     *  |       .
     *  |       |
     *  |       del
     *  |     /    \
     *  |    .      .
     *  |         /   \
     *  |       suc    .
     *  |         \
     *  |          .
     *  ---------- ---------- ----------
     *
     * @param node 起点
     * @param key  删除 目标 key
     * @return 剩余的孩子 或 null
     */
    private TreeNode doDelete(TreeNode node, int key) {
        if (node == null) return null;
        
 		// 继续向左 意味着当前节点 node 不会被删除,但是 node.left作为递归新起点
        if (key < node.val) {
        	// 会返回 从 node.left 开始 key 节点 被删除后的子树
            node.left = deleteNode(node.left, key); 
            return node;
        }

        if (key > node.val) { // 继续向右 同上
            node.right = deleteNode(node.right, key);
            return node;
        }

        // node.val == key
        // 只有右孩子 将有孩子返回给 上层(父节点)
        if (node.left == null) {
            return node.right;
        }
        // 只有左孩子 将有孩子返回给 上层(父节点)
        if (node.right == null) {
            return node.left;
        }

        // 有两个孩子 找到后继
        TreeNode successor = node.right;

        while (successor.left != null) {
            successor = successor.left;
        }

        successor.right = deleteNode(node.right, successor.val);
        successor.left = node.left;

        return successor;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值