1. 题目来源
链接:删除二叉搜索树中的节点
来源:LeetCode
2. 题目说明
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
说明:
要求算法时间复杂度为 O(h),h 为树的高度。
请注意:
石子的数量 ≥ 2 且 < 1100;
每一个石子的位置序号都是一个非负整数,且其 <
2
31
2^{31}
231;
第一个石子的位置永远是0。
示例:
key = 3
5
/ \
3 6
/ \ \
2 4 7
给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
5
/ \
4 6
/ \
2 7
另一个正确答案是 [5,2,6,null,4,null,7]。
5
/ \
2 6
\ \
4 7
3. 题目解析
方法一:常规二叉搜索树删除节点法
首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
- 要删除的结点无孩子结点
- 要删除的结点只有左孩子结点
- 要删除的结点只有右孩子结点
- 要删除的结点有左、右孩子结点
看起来有待删除节点有 4 中情况,实际情况 a 可以与情况 b 或者 c 合并起来,因此真正的删除过程有三种情况:
- 删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点
- 删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点
- 在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题
参见代码如下:
// 执行用时 :28 ms, 在所有 C++ 提交中击败了84.50%的用户
// 内存消耗 :12.8 MB, 在所有 C++ 提交中击败了23.01%的用户
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == nullptr)
return nullptr;
TreeNode* cur = root;
TreeNode* parent = nullptr;
while (cur) {
if (cur->val == key)
break;
else if (cur->val > key) {
parent = cur;
cur = cur->left;
}
else {
parent = cur;
cur = cur->right;
}
}
if (cur == nullptr)
return root;
if (cur->left == nullptr && cur->right == nullptr) {
if (cur != root) {
if (parent->left == cur)
parent->left = nullptr;
else
parent->right = nullptr;
}
else
root = nullptr;
delete cur;
cur = nullptr;
}
else if (cur->left == nullptr) {
if (cur != root) {
if (parent->left == cur)
parent->left = cur->right;
else
parent->right = cur->right;
}
else
root = cur->right;
delete cur;
cur = nullptr;
}
else if (cur->right == nullptr) {
if (cur != root) {
if (parent->left == cur)
parent->left = cur->left;
else
parent->right = cur->left;
}
else {
root = cur->left;
}
delete cur;
cur = nullptr;
}
else {
TreeNode* leftMost = cur->left;
parent = cur;
while (leftMost->right) {
parent = leftMost;
leftMost = leftMost->right;
}
cur->val = leftMost->val;
if (parent->right == leftMost)
parent->right = leftMost->left;
else
parent->left = leftMost->left;
delete leftMost;
leftMost = nullptr;
}
return root;
}
};
方法二:递归解法
思路与方法一相同,只不过以递归方式实现:
- 首先判断根节点是否为空。
- 根据 BST 的 左<根<右 的性质,在树中快速定位到要删除的结点,即对于当前结点值不等于 key 的情况,根据大小关系对其左右子结点分别调用递归函数
- 若当前结点就是要删除的结点,先判断若有一个子结点不存在,就将 root 指向另一个结点,如果左右子结点都不存在,那么 root 就赋值为空
- 难点就在于处理左右子结点都存在的情况,需要在右子树找到最小值(后继节点),即右子树中最左下方的结点,或者在左子树中找到最大值(前驱节点),即左子树中最右下方节点
- 然后将前驱 / 后继节点与待删节点进行值替换
- 然后再在左 / 右子树中调用递归函数来删除这个前驱 / 后继节点即可
参见代码如下:
// 执行用时 :140 ms, 在所有 C++ 提交中击败了66.29%的用户
// 内存消耗 :35.5 MB, 在所有 C++ 提交中击败了36.81%的用户
class Solution {
public:
bool canCross(vector<int>& stones) {
unordered_map<int, unordered_set<int>> m;
vector<int> dp(stones.size(), 0);
m[0].insert(0);
int k = 0;
for (int i = 1; i < stones.size(); ++i) {
while (dp[k] + 1 < stones[i] - stones[k]) ++k;
for (int j = k; j < i; ++j) {
int t = stones[i] - stones[j];
if (m[j].count(t - 1) || m[j].count(t) || m[j].count(t + 1)) {
m[i].insert(t);
dp[i] = max(dp[i], t);
}
}
}
return dp.back() > 0;
}
};
方法三:迭代解法
思路如下:
- 通过 BST 的性质来快速定位要删除的结点,如果没找到直接返回空
- 遍历的过程要记录上一个位置的结点 pre,如果 pre 不存在,说明要删除的是根结点
- 如果要删除的结点在 pre 的左子树中,那么 pre 的左子结点连上删除后的结, 反之 pre 的右子结点连上删除后的结点
- 在删除函数中,首先判断空节点,若为空,直接返回空指针
- 否则检测若右子结点不存在,直接返回左子结点即可,因为没有右子树就不会牵扯到调整树结构的问题
- 若右子结点存在,需要找到右子树中的最小值,即右子树中的最左子结点(后继节点),用一个 while 循环找到即可
- 然后将要删除结点的左子结点连到后继节点(右子树的最左子结点的左子结点)上即可,说的有点绕,大家仔细体会一下…
- 最后返回要删除结点的右子结点即可
文字表述确实比较绕,请大家自行带例子一步一步观察就会很清晰明了。
参见代码如下:
// 执行用时 :20 ms, 在所有 C++ 提交中击败了98.71%的用户
// 内存消耗 :12.7 MB, 在所有 C++ 提交中击败了43.44%的用户
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root)
return nullptr;
TreeNode *cur = root, *pre = nullptr;
while (cur) {
if (cur->val == key)
break;
pre = cur;
if (cur->val > key)
cur = cur->left;
else
cur = cur->right;
}
if (!pre)
return del(cur);
if (pre->left && pre->left->val == key)
pre->left = del(cur);
else
pre->right = del(cur);
return root;
}
TreeNode* del(TreeNode* node) {
if (!node)
return nullptr;
if (!node->right)
return node->left;
TreeNode *t = node->right;
while (t->left)
t = t->left;
t->left = node->left;
return node->right;
}
};
方法四:二叉树通用解法
下面来看一种对于二叉树通用的解法,适用于所有二叉树,所以并没有利用 BST 的性质,而是遍历了所有的结点,然后删掉和 key 值相同的结点
参见代码如下:
// 执行用时 :20 ms, 在所有 C++ 提交中击败了98.71%的用户
// 内存消耗 :12.9 MB, 在所有 C++ 提交中击败了43.44%的用户
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root)
return NULL;
if (root->val == key) {
if (!root->right)
return root->left;
else {
TreeNode *cur = root->right;
while (cur->left)
cur = cur->left;
swap(root->val, cur->val);
}
}
root->left = deleteNode(root->left, key);
root->right = deleteNode(root->right, key);
return root;
}
};