题目描述
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;
}
}