235. 二叉搜索树的最近公共祖先
本题使用 二叉树的最近公共祖先 这题的代码同样能解,但本题的给出了额外的可利用的信息,这棵树是二叉搜索树,利用这个信息可以减少搜索的复杂度。
本题是二叉搜索树,二叉搜索树是有序的,在有序树里,如果判断一个节点的左子树里有p,右子树里有q, 那么这个节点就是p,q的公共祖先。
换句话说,如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p。
那么只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是p 和 q的公共祖先。并且,如果能确认到 p和q 分别是在左子树和右子树中,那么该节点必然是最近的公共祖先,因为,从该节点开始,再往左子树遍历,那么将确认不到 q,因为 q 在该节点的右子树上,p 也同理。所以只要判断到一个节点的左子树里有p,右子树里有q,那么该节点就是最近公共祖先。
再利用上二叉搜索树的特性,如果 p, q 的 val 都比当前节点 val 大的话,那么 p, q 只会出现在当前节点的右子树上; 都比当前节点 val 小时,则在左子树上。
通过这些特性,能够减少搜索时需要遍历的节点数。701.二叉搜索树中的插入操作
class Solution {
public:
TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q){
if(cur == NULL) return NULL;
TreeNode* left = NULL;
TreeNode* right = NULL;
if(cur->val > p->val && cur->val > q->val){
left = traversal(cur->left, p, q);
if(left) return left;
}
if(cur->val < p->val && cur->val < q->val){
right = traversal(cur->right, p, q);
if(right) return right;
}
return cur;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
return traversal(root, p, q);
}
};
701.二叉搜索树中的插入操作
二叉搜索树中插入节点的插入方法有很多,可以插到中间,开头,之后再维护整棵树,使其满足二叉搜索树的性质。我最开始就是这么想的,像维护堆一样,维护这棵树。
但其实这么做就复杂了。
这题的技巧在于,在二叉搜索树中插入节点的操作,插入的节点可以直接当作都是叶子节点,只要遍历二叉搜索树,找到满足二叉搜索树性质的空节点 插入元素就可以了,这样插入节点并不会影响整棵树的性质,还省了维护的过程。
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(!root){
TreeNode* node = new TreeNode(val);
return node;
}
if(root->val > val) root->left = insertIntoBST(root->left, val);
if(root->val < val) root->right = insertIntoBST(root->right, val);
return root;
}
};
450.删除二叉搜索树中的节点
这题的思路是,将将要删除节点的左子树插入到其右子树的最左侧节点,这样就能保证不破坏二叉搜索树的性质。
class Solution {
public:
TreeNode* insertIntoLeftmost(TreeNode* cur, TreeNode* node){
if(!cur){
return node;
}
TreeNode* last = cur;
while (last->left) {
last = last->left;
}
last->left = node; // 将 node 插入到最左侧叶子节点的左侧
return cur;
}
TreeNode* deleteNode(TreeNode* root, int key) {
if(root == nullptr) return root;
if(root->val == key){
if (root->left == nullptr) {
TreeNode* rightChild = root->right;
delete root;
return rightChild;
}
if (root->right == nullptr) {
TreeNode* leftChild = root->left;
delete root;
return leftChild;
}
TreeNode* node = root->right; // 获取右子树
node = insertIntoLeftmost(node, root->left); // 将左子树插入右子树的最左侧
delete root;
return node;
}
if(root->val > key) root->left = deleteNode(root->left, key);
if(root->val < key) root->right = deleteNode(root->right, key);
return root;
}
};
但其实,更常见的删除思路是用其右子树中的最小节点(或左子树中的最大节点)来替换要删除的节点,然后删除那个最小(或最大)节点。
这里我分成了两个函数来实现,一个负责查找,一个负责删除,逻辑部分分成两块,各函数独立完成自己的任务。
这些么写之后需要在delete函数中,调用完getminimum后再调用delete来删除替换过来的节点。如下所示。
TreeNode* minNode = getMinimum(root->right);
root->val = minNode->val;
root->right = deleteNode(root->right, minNode->val);
在执行 deleteNode(root->right, minNode->val)
时,你实际上是:
- 首先确定了最小节点的位置。
- 然后替换值,并且递归地调用
deleteNode
来精确删除那个位置的节点。因为这个最小节点没有左子节点,这个删除操作变得非常直接和简单。
完整代码如下所示
TreeNode* getMinimum(TreeNode* node) {
while (node->left != nullptr) {
node = node->left;
}
return node;
}
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root) return nullptr;
if (key < root->val) {
root->left = deleteNode(root->left, key);
} else if (key > root->val) {
root->right = deleteNode(root->right, key);
} else {
if (!root->left) {
TreeNode* rightChild = root->right;
delete root;
return rightChild;
}
if (!root->right) {
TreeNode* leftChild = root->left;
delete root;
return leftChild;
}
// 当存在两个子节点时,找到右子树中最小的节点并替换当前节点的值,然后删除那个最小的节点
TreeNode* minNode = getMinimum(root->right);
root->val = minNode->val;
root->right = deleteNode(root->right, minNode->val);
}
return root;
}