leetcode669.修剪二叉搜索树(递归+迭代)

修剪二叉搜索树

在这里插入图片描述
思路:

  1. root->val < low,root在区间外。去root的右子树去找有无区间内的,因为root的左子树只会比root->val更小;
  2. root->val > high,root在区间外。去root的左子树去找有无区间内的,因为root的右子树只会比root->val更大;

1.递归法

误区:如下代码是错误解法
之前做过删除二叉搜索树中的节点这题之后,我们很容易能写出一个节点被删除,要将root->right
连接到要删除节点的父节点,这样我们就遗漏了后面要删除的节点,如下图。

TreeNode* trimBST(TreeNode* root, int low, int high) {
    if(root == nullptr) return nullptr;
    
    // 如果root的值小于low, 那么root包括root的左子树都不是我们要保留的
    // 所以要将root的右子树的根节点递给上一层栈帧中的root->left
    if(root->val < low)
        return root->right;
    if(root->val > high)
		return root->left;
    root->left = trimBST(root->left, low, high);
    root->right = trimBST(root->right, low, high);     
    return root;
}

在这里插入图片描述
所以我们要在子树中继续去找,将整棵树的问题转化为子树的子问题,它们的操作逻辑是一样的。具体递归展开如下图:

从递归最深的节点开始向上回溯,2的左和2的右都接收到返回值之后,就意味着1的删除;然后2将它自己返回作为3的左的接收值,3的右接受空;再然后3将自己返回作为8的左的接收值,这意味着0的删除;右子树的逻辑也如此…
在这里插入图片描述
正确解法:

TreeNode* trimBST(TreeNode* root, int low, int high) {
    if(root == nullptr) return nullptr;
    
    // 如果root的值小于low, 那么root包括root的左子树都不是我们要保留的
    // 所以要递归遍历root的右子树, 返回不会被修剪的子树根节点
    if(root->val < low)
    {
        TreeNode* Right = trimBST(root->right, low, high);
        return Right;
    }
    if(root->val > high)
    {
        TreeNode* Left = trimBST(root->left, low, high);
        return Left;
    }
    // 上一层递归中的root要接受下一层的返回值
    root->left = trimBST(root->left, low, high);
    root->right = trimBST(root->right, low, high);
    // 节点的左和右接受完毕,返回当前层的节点    
    return root;
}

时间复杂度:O(N)。最坏的情况是要遍历N个节点的。
空间复杂度:O(N)。最坏的情况递归调用的深度为N。

2.迭代法

如果使用迭代法,头结点一定要先处理,因为如果头结点需要删除的话,就连带着它的一颗子树也处理了,这样子我们再更新头结点,直到头结点不需要删除为止。这样我们后面考虑问题只需要从孩子节点开始考虑即可,运用双指针法,当cur需要删除的时候,将cur->left或者cur->right连接到prev

双指针如何更新呢?
如下拿左子树为例:

对于有删除的情况,prev是不会变的,原节点要删除,它等待着接受新的节点,而cur则要更新 (插一嘴:为什么往right跑呢?因为left只会比cur还要小,而cur已经小于low了,往right跑是因为右边的节点还不确定)。
对于没有删除的情况,prev占据cur之前的位置,cur往left跑。(为什么这个又是往left跑?因为此时的cur没有删,也就是大于low,我当然要找比cur要小的节点才能知道是否需要删除啊,而比cur要小的节点不就是在cur的左子树吗)

下面写出代码实例:

对于节点的删除传统的写法是这样的:如果当初cur连在prev->left,那么我的新节点也就要连在prev->left;如果当初cur连在prev->right,那么我的新节点也就要连在prev->right。这是二叉搜索树的性质决定的。

TreeNode* cur = root->left, *prev = root;
// 此时root已经在区间[low,high]中了, 接下来处理左孩子小于low的情况
while(cur)
{
    if(cur->val < low)
    {
        // 删除操作
        if(prev->left == cur) prev->left = cur->right;
        if(prev->right == cur) prev->right = cur->right;
        cur = cur->right;
    }
    else
    {
        prev = cur;
        cur = cur->left;
    }  
}

其实我们不用写两个判断,因为我们确定cur一定连接在prev->left,因为我每次都是往左子树去找的。

整体代码如下:

TreeNode* trimBST(TreeNode* root, int low, int high) 
{
	// 先处理头结点
	while(root != nullptr && (root->val < low || root->val > high))
	{
	    if(root->val < low)
	        root = root->right; // root及其左子树都要删掉
	    else
	        root = root->left; // root及其右子树都要删掉
	}
	if(root == nullptr) return nullptr;
	
	TreeNode* cur = root->left, *prev = root;
	// 此时root已经在区间[low,high]中了, 接下来处理左孩子小于low的情况
	while(cur)
	{
	    if(cur->val < low)
	    {
	        // 删除操作
	        // 为什么是链接到prev的左, 因为我每次都是往左子树去找的
	        // 那么自然cur就链接着prev的左了
	        prev->left = cur->right;
	        cur = cur->right;
	    }
	    else
	    {
	        prev = cur;
	        cur = cur->left;
	    }  
	}
	
	// 接下来处理右孩子大于high的情况
	cur = root->right, prev = root;
	while(cur)
	{
	    if(cur->val > high)
	    {
	        // 删除操作
	        prev->right = cur->left;
	        cur = cur->left;
	    }
	    else
	    {
	        prev = cur;
	        cur = cur->right;
	    } 
	}
	return root;
}

时间复杂度:O(N)。最坏的情况是要遍历N个节点的。
空间复杂度:O(1)。只创建了常数的变量。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值