力扣刷题450. 删除二叉搜索树中的节点 java语言版

原题链接: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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值