难度中等1037
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
示例 1:
输入:root = [5,3,6,2,4,null,7], key = 3 输出:[5,4,6,2,null,null,7] 解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。 一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。 另一个正确答案是 [5,2,6,null,4,null,7]。
示例 2:
输入: root = [5,3,6,2,4,null,7], key = 0 输出: [5,3,6,2,4,null,7] 解释: 二叉树不包含值为 0 的节点
示例 3:
输入: root = [], key = 0 输出: []
提示:
- 节点数的范围
[0, 104]
. -105 <= Node.val <= 105
- 节点值唯一
root
是合法的二叉搜索树-105 <= key <= 105
进阶: 要求算法时间复杂度为 O(h),h 为树的高度。
递归法
- 使用递归法,核心就是将删除完结点后返回新头结点给父节点构成一颗新的二叉搜索树
- 删除节点有四种情况(假设当前节点为删除的节点root)
- root.left==null&&root.right==null,把root删除,新的头结点就是null.
- root.left!=null&&root.right==null,把root删除,返回root.left
- root.left==null&&root.right!=null,把root删除,返回root.right
- root.left!=null&&root.right!=null,就要找到右树中的最左节点cur(比root的值大一点),然后将root.left接到cur.left上,返回root.right即可(也可以找左树中的最右比root的值小一点)
- 由于二叉搜索树当根节点值大于key,去左树上删除,当根节点值小于key,去右树上删除.
本题利用二叉搜索树的特性 都是根节点的值>左子树的所有节点,根节点的值<右子树的所有节点
- 一个是,找右树上的最左(或者左树上的最右)
- 一个是,当根节点值大于key,去左树上删除,当根节点值小于key,去右树上删除.
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if(root==null) return null;
if(root.val==key) {
if(root.left==null&&root.right==null) {
//叶子结点直接把当前节点删除即可,也就是返回null给父节点接上
return null;
}else if(root.left==null&&root.right!=null) {
//要删除当前节点,当前节点左为null,右不为null
//那就把当前节点的右子树返回去给父节点接上,也就把当前节点删除了
return root.right;
}else if(root.left!=null&&root.right==null) {
//要删除当前节点,当前节点右为null,左不为null
//那就把当前节点的左子树返回去给父节点接上,也就把当前节点删除了
return root.left;
}else {
//左右子树都不为null,要删除当前节点,那就需要找一个比当前节点大一点的
//或者比当前节点小一点的数替代当前节点.帮其把左子树或者右子树保存下来
TreeNode cur = root.right;//找到右子树的最左节点(比当前节点大一点)
while(cur.left!=null){
cur = cur.left;
}
//将当前节点的左子树接到cur的左子树上
cur.left = root.left;
//返回子树新头部
return root.right;
}
}
//去左子树找删除的结点,删除完毕后把删除好的头结点接在左孩子上
if(root.val>key){
root.left = deleteNode(root.left,key);
}
//去右子树找删除的结点,删除完毕后把删除好的头结点接在右孩子上
if(root.val<key){
root.right = deleteNode(root.right,key);
}
return root;
}
}
迭代法
- 首先根据二叉搜索树的特性找到要删除的节点值,同时记录父节点
- 删除节点分为以下几种情况(删除节点为cur ,父节点为parent)
- cur.left==null
- cur==root
- parent.left==cur
- parent.right==cur
- cur.right==null
- cur==root
- parent.left==cur
- parent.right==cur
- cur.left!=null&&cur.right!=null 使用替罪羊法删除该cur节点
- 使用替罪羊的主要思想就是找到跟当前节点cur差不多大的节点,要不比cur大一点,要不比cur小一点,也就是cur的前一个或者后一个--->中序遍历有序性就可以找到(假设为target)
- 找到target之后将cur要删除的值替换为该值(大一点或小一点),然后将target删除
- cur.left==null
class Solution {
public TreeNode deleteNode(TreeNode root,int key) {
if(root==null) return null;
if(root.val==key){
if(root.left==null&&root.right==null){
return null;
}else if(root.left==null){
return root.right;
}else if(root.right==null){
return root.left;
}
}
TreeNode cur = root;
TreeNode parent = null;
while (cur != null) {
if(cur.val < key) {
parent = cur;
cur = cur.right;
}else if(cur.val == key) {
//这里开始删除!
removeNode(root,parent,cur);
return root;
}else {
parent = cur;
cur = cur.left;
}
}
return root;
}
private void removeNode(TreeNode root,TreeNode parent,TreeNode cur) {
if(cur.left==null){
if(cur==root) {
root = cur.right;
}else if(cur==parent.left) {
parent.left = cur.right;
}else{
parent.right = cur.right;
}
}else if(cur.right==null){
if(cur==root) {
root = cur.left;
}else if(cur==parent.left) {
parent.left = cur.left;
}else{
parent.right = cur.left;
}
}else {
TreeNode targetParent = cur;
TreeNode target = cur.right;
while(target.left!=null){
targetParent = target;
target = target.left;
}
cur.val = target.val;
if(targetParent.left==target){
targetParent.left = target.right;
}else {
targetParent.right = target.right;
}
}
}
}