原题链接:450. 删除二叉搜索树中的节点
最初的想法:
删除一个节点,先是看这个节点存在与否,若存在则删掉。节点只指向左右节点而不指向父结点,那么要删除这个节点就得找到他的父结点,通过父结点找到他。所以父结点是否存在就可以分情况讨论了。 叶子节点删起来很简单,为此想到根据度的不同划分不同的删除模式。度为1,直接跨过它连下去就好,度为2比较麻烦,不好删除,所以转换思路,将他的后续或者前驱结点的值赋值过去,再把前驱或者后继节点删掉,间接删除了度为2的节点。
深入思考:
度为2的节点的前驱后续结点怎么删除呢?他们如果还是度为2,那我岂不是一直替换一直删,直到度为0或者1,多麻烦。其实,本题的背景下前驱后继节点一定是度为0或者1的节点,原因如下:度为2的节点左右子树都存在,那前驱结点就是左子树的最右端,后继结点就是右子树的最左端,我们用反证法,如果他们的度为2,势必还有比他们更左更右的节点,那他们就不是最左端最右端的节点了,与前驱后继的定义相违背,所以不可能。
代码结构:
函数1 找父结点
函数2 找后继节点
主函数
******父结点不存在
************是根节点
************ 否
****** 存在
************ 度=2 -->0 1
收获:
这个代码并不很难,但我写了非常久,一直在力扣上面根据提示调错。
1.清晰的分情况讨论的思路。
2.if if并列 if if 嵌套 if else还有if( && )非常有讲究,不能瞎用,否则会 导致情况讨论不全。
3.语句可能简单,但是顺序至关重要,谁在前谁在后要考虑清楚,尤其是大括号里面的几句,有的只是看起来先后无关了。
4.尽管前驱后继有别的很多情况,但是因为题目条件的限制,这里只是出现了最简单的情况,即左子树极右和右子树极左。
5.空指针异常意味着情况考虑不周,对特定数据异常,本质不是指针异常,是代码逻辑有问题。比如root=null没考虑,root.val自然会空指针异常。
代码和详细注释:
public class _450_删除二叉搜索树中的节点 {
/**
* 找到待删除元素的父结点
*/
public TreeNode findParent(TreeNode root,int key) {
TreeNode node = root;
//针对1 null 2 的情况,也就是根值相等,while只是讨论了左右值
if(root.val == key) return null;
while(node.left != null || node.right != null) {
//if并列 不能&& 或者else 因为会出现进入一个if不都满足条件而无法进入另一个if的情况
if(node.left != null )
if(key == node.left.val) return node;
if(node.right != null)
if( key == node.right.val) return node;
if(key > node.val) {
node = node.right;
}
else {
node = node.left;
}
}
//出来说明到了叶子节点,而这个叶子节点被他的父结点判断过不相等了 所以查找结束
return null;
}
public TreeNode successor(TreeNode root,TreeNode node) {
//度为2的节点一定有右子树,后继节点就是右子树的最左侧,所以一定有后继节点
node = node.right;
while(node.left != null) {
node = node.left;
}
return node;
}
public TreeNode deleteNode(TreeNode root, int key) {
//树空
if(root == null) return null;
//找到父结点,根据父结点分情况讨论
TreeNode parent = findParent(root,key);
//初始化node 只有找到他的父结点才能初始化他
TreeNode node = null;
if(parent != null){
if(parent.right != null) {
if(parent.right.val == key)
node = parent.right;
}
//不能else if 因为右边不空但是不等要进入下一个
if(parent.left != null ) {
if(parent.left.val == key)
node = parent.left;
}
}
//找不到父结点
if(parent == null) {
//没这个点 直接返回
if(root.val != key) return root;
//根节点是要删的点
if(root.val == key) {
//只有一个节点的树
if(root.right == null && root.left == null) {
root = null;
return root;
}
//根的单侧有节点
if(root.left != null && root.right == null) {
root = root.left;
return root;
}
if(root.right != null && root.left == null) {
root = root.right;
return root;
}
//根的两侧都有节点
if(root.right != null && root.left != null) {
TreeNode replacement = successor(root, root);
parent = findParent(root, replacement.val);
int ms = root.val;
root.val = replacement.val;
node = replacement;//让下面统一化处理node
node.val = ms;
}
/*以上代码非常易错的地方在于findParent的位置,不能值替换了找
因为替换以后就不是一颗二叉搜索树了,替换只是为了让下面一起处理Node方便
ms的使用也很关键,因为删除度=0或1的节点时要根据值来判断是在左还是在右
*/
}
}
//找到了父结点 说明删除的不是根节点
//删除度为2的节点
if( node.right != null && node.left != null) {
TreeNode replacement = successor(root, node);
parent = findParent(root, replacement.val);
int sm = node.val;
node.val = replacement.val;
node = replacement;//同上 node变为度=0或1的节点
node.val = sm;
}
//下面要删的都是度=1或者0的节点
//看父结点右侧
if(parent.right != null) {
if(parent.right.val == key){
//删除叶子节点
if(node.left == null && node.right == null) {
parent.right = null;
}
//删除度为1的节点
if(node.left != null && node.right == null) {
parent.right = node.left;
}
if(node.right != null && node.left == null) {
parent.right = node.right;
}
}
}
//看左侧
if(parent.left != null) {
if(parent.left.val == key){
//删除叶子节点
if(node.left == null && node.right == null) {
parent.left = null;
}
//删除度为1的节点
if(node.left != null && node.right == null) {
parent.left = node.left;
}
if(node.right != null && node.left == null) {
parent.left = node.right;
}
}
}
return root;
}
}