二叉搜索树的最近公共祖先
题目:给定二叉搜索树,找到两个指定节点的最近公共祖先。
思路:利用有序性!!!因为有序树,所以如果中间节点是q和p的公共祖先,那么中节点的数组一定是在[p,q]区间的。
即中节点 >p&&中节点<q
或者中节点>q&&中节点<p.
从上到下遍历,第一次遇到的在这个区间内的节点就是p和q的最近公共祖先。不用担心p和q不在同一子树上。
递归法:
1.确定递归函数返回参数和返回值。参数是节点p和q.返回值是最近公共祖先TreeNode*cur。
2.确定终止条件。如果节点最后遍历为空,就返回。这个条件在本题多余,由题意知一定会找到p和q.
3.确定单层递归的逻辑。寻找区间[p->val, q->val]。两种情况都要判断(p>q,p<q),整棵树都要搜索。第三种情况是,p或q本身就是最近公共祖先。
class Solution{
private:
TreeNode* traversal(TreeNode* cur,TreeNode* p,TreeNode* q){
if(cur == NULL)retrun cur;
if(cur->val > p->val && cur->val > q->val){//第一种情况,遍历左树
TreeNode* left = traversal(cur->left,p,q);
if(left != NULL){
return left;
}
}
if(cur->val < p->val && cur->val < q->val){
TreeNode* right = traversal(cur->right,p,q);
if(right != NULL){
return right;
}
}
return cur;
}
public:
TreeNode* lowestCommonAncestor(TreeNode* root,TreeNode* p,TreeNode* q){
return traversal(root,p,q);
}
};
精简:
class Solution{
public:
TreeNode* lowestCommonAncestor(TreeNode* root,TreeNode* p,TreeNode* q){
if(root->val > p->val && root->val > q->val){
return lowestCommonAncestor(root->left,p,q);
}else if(root->val < p->val && root->val < q->val){
return lowestCommonAncestor(root->right,p,q);
}else return root;
}
};
迭代法:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
while(root) {
if (root->val > p->val && root->val > q->val) {
root = root->left;
} else if (root->val < p->val && root->val < q->val) {
root = root->right;
} else return root;
}
return NULL;
}
};
二叉搜索树中的插入操作
题目:根节点和要插入树中的值,插进二叉搜索树,保存,返回任意有效的二叉搜索树。
思路:
可以不改变树的原有结构,按照二叉搜索树的特点遍历,遇到空节点插入就可以。
递归:
>>确定递归函数参数以及返回值
参数就是根节点指针,以及要插入元素,这里递归函数科尔已有返回值,也可以没有返回值,但是没有返回值,实现会比较麻烦。
有返回值的情况下,可以利用返回值完成新加入节点于其父节点的赋值操作。
递归函数的返回类型为节点类型TreeNode*.
TreeNode* insertIntoBST(TreeNode* root,int val)
>>确定终止条件
终止条件就是找到遍历的节点为null的时候,就是要插入节点的位置了,并把插入的节点返回。
代码如下:
if(root == NULL){
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;
以上是通过递归函数书返回值完成新加入节点的父子关系赋值操作,下一层将加入节点返回,本层用root->left或者root->right将其接住。
class Solution{
public:
TreeNode* insertIntoBST(TreeNode* root,int val){
if(root == NULL){
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;
}
}
不用递归函数返回值,找到插入的节点位置,直接让其父节点指向插入节点,结束递归,也可以。
那么递归函数定义:
TreeNode* parent;//记录遍历节点的父节点
void traversal(TreeNode* cur,int val)
没有返回值,需要记录上一个节点parent,遇到空节点了,就让parent左孩子或者右孩子指向新插入的节点。然后结束递归。
class Solution{
public:
TreeNode* parent;
void traversal(TreeNode* cur,int val){
if(cur == NULL){
TreeNode* node = new TreeNode(val);
if(val > parent->val)parent->right = node;
else parent->left = node;
return;
}
parent = cur;
if(cur->val > val)traversal(cur->left,val);
if(cur->val < val)traversal(cur->right,val);
return;
}
public:
TreeNode* insertIntoBST(TreeNode* root,int val){
parent = new TreeNode(0);
if(root == NULL){
root = new TreeNode(val);
}
traversal(root,val);
return root;
}
};
对比发现,通过递归函数的返回值来完成父子节点的赋值可以带来便利。
迭代法:在迭代中更需要记录当前遍历的节点的父节点,才能做插入节点的操作。利用记录pre和cur两个指针的技巧。
class Solution{
public:
TreeNode* insertIntoBST(TreeNode* root,int val){
if(root == NULL){
TreeNode* node = new TreeNode(val);
return node;
}
TreeNode* cur = root;
TreeNode* parent = root;//记录上一节点
while(cur != NULL){
parent = cur;
if(cur->val > val)cur = cur->left;
else cur = cur->right;
}
TreeNode* node = new TreeNode(val);
if(val < parent->val)parent->left = node;//此时是用parent节点的进行赋值
else parent->right = node;
return root;
}
};
删除二叉搜索树中的节点
题目:给定二叉搜索树的根节点root和一个值key,删除二叉搜索树中的key对应的节点,并保证二叉搜索树的性质不变。返回根节点的引用。
思路:
1.递归
递归函数返回值,类似于增加节点。
TreeNode* deleteNode(TreeNode* root, int key)
确定终止条件
遇到空返回,其实这也说明没找到删除的节点,遍历到空节点直接返回了
if(root == nullptr)return root;
确定单层递归的逻辑
有5种情况:
第一种,没找到删除的节点,遍历到空节点直接返回了,找到删除的节点
第二种,左右孩子都为空(叶子节点),直接删除接节点,返回NULL为根节点。
第三种,第四种,删除节点的左(右)孩子为空,右(左)孩子不为空,删除节点,右(左)孩子补位,返回右(左)孩子为根节点
第五种,左右孩子节点都不为空,则将删除节点的左子树节点(左孩子)放到删除节点的左子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
if(root->val == key){
if(root->left == nullptr)return root->right;
else if(root->right == nullptr)return root->left;
else{
TreeNode* cur = root->right;
while(cur->left != nullptr){
cur = cur->left;
}
cur->left = root->left;
TreeNode* tmp = root;
root = root->right;
delete tmp;
return root;
}
}
相当于把新节点返回给上一层,上一层就要用root->left或者root->right接住,
if(root->val > key) root->left = deleteNode(root->left,key);
if(root->val < key)root->right = deleteNode(root->right,key);
return root;
class Solution{
public:
TreeNode* deleteNode(TreeNode* root,int key){
if(root == nullptr)return root;
if(root->val == key){
if(root->left == nullptr && root->right == nullptr){
delete root;
return nullptr;
}
else if(root->left == nullptr){
auto retNode = root->right;
delete root;
return retNode;
}
else if(root->right == nullptr){
suto retNode = root->left;
delete root;
return retNode;
}
else{
TreeNode* cur = root->right;
while(cur->left != nullptr){
cur = cur->left;
}
cur->left = root->left;
TreeNode* tmp = root;
root = root->right;
delete tmp;
return root;
}
}
if(root->val > key)root->left = deleteNode(root->left,key);
if(root->val < key)root->right = deleteNode(root->right,key);
return root;
}
};
普通二叉树节点删除
不能利用搜索树的特性,遍历整棵树。用交换值的操作来删除目标节点。
代码中目标节点(要删除的节点)被操作了两次:
第一次是和目标节点的右子树最左面节点交换。
第二次直接被null覆盖了。
class Solution{
public:
TreeNode* deleteNode(TreeNode* root,int key){
if(root == nullptr)return root;
if(root->val == key){
if(root->right == nullptr){//第二次覆盖
return root->left;
}
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;
}
};
迭代法
删除节点的迭代法还是复杂一些的,关键在于删除节点的操作
class Solution{
private:
//将目标节点(删除节点)的左子树放到 目标节点的右子树的最左面节点的左孩子位置上
//返回目标节点右孩子为新的根节点
TreeNode* deleteOneNode(TreeNode* target){
if(target == nullptr)return target;
if(target->right == nullptr)return target->left;
TreeNode* cur = target->right;
while(cur->left){
cur = cur->left;
}
cur->left = target->left;
return target->right;
}
public:
TreeNode* deleteNode(TreeNode* root,int key){
if(root == nullptr)return root;
TreeNode* cur = root;
TreeNode* pre = nullptr;
while(cur){
if(cur->val == key)break;
pre = cur;
if(cur->val > key)cur = cur->left;
else cur = cur->right;
}
if(pre == nullptr){
return deleteOneNode(cur);
}
if(pre->left && pre->left->val == key){
pre->left = deleteOneNode(cur);
}
if(pre->right && pre->right->val == key){
pre->right = deleteOneNode(cur);
}
return root;
}
};