一、二叉搜索树中的最近公共祖先
链接:力扣
描述:
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
思路:
1、利用回溯从底向上搜索,遇到一个节点的左子树里有p,右子树里有q,那么当前节点就是最近公共祖先。
2、利用二叉搜索树的特性,因为是有序树,所以如果中间节点是 q 和 p 的公共祖先,那么中节点的数组 一定是在 [p, q]区间的。那么只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中,则一定可以说明该节点cur就是q 和 p的公共祖先。 那问题来了,一定是最近公共祖先吗?是的。
代码如下:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
{
if (!root)
{
return NULL;
}
if (root == p || root == q)
{
return root;
}
//中序遍历
TreeNode* left = lowestCommonAncestor(root->left, p, q);//左
if (left == NULL)
{
//左边没找到结点,说明一定在右边
return lowestCommonAncestor(root->right, p, q);
}
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (right)
{
return root;
}
else
{
return left;
}
return NULL;
}
};
二、 二叉搜索树中的插入操作
链接:力扣
描述:
给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
思路:只要遍历二叉搜索树,找到空节点插入元素就可以了,那么这道题其实就简单了。
注意return的结点就是新的结点,用上一层的结点的左或者右指针接住就行。
代码如下:
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);
}
else if (root->val < val)
{
root->right = insertIntoBST(root->right, val);
}
return root;
}
};
运行如下:
补充:第一次写的代码如下:
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (!root)
{
return NULL;
}
if (root->val > val)
{
TreeNode* left = insertIntoBST(root->left, val);
if (left == NULL)
{
TreeNode* node = new TreeNode(val);
root->left = node;
}
}
else if (root->val < val)
{
TreeNode* right = insertIntoBST(root->right, val);
if (right == NULL)
{
TreeNode* node = new TreeNode(val);
root->right = node;
}
}
return root;
}
};
出现了bug:忽略了这种情况。后面改进了
三、删除二叉搜索树中的结点
链接:力扣
描述:给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
思路如下:按以下情况进行分析即可
- 第一种情况:没找到删除的节点,遍历到空节点直接返回了
- 找到删除的节点
- 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
- 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
- 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
- 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
代码如下:
#include <iostream>
using namespace std;
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode* left, TreeNode* right) : val(x), left(left), right(right) {}
};
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key)
{
if (!root)
{//未找到key,返回到上一层的空指针
return NULL;
}
if (root->val == key)
{//找到的情况
if (root->left == NULL && root->right == NULL)
{
//叶子结点的情况
delete root;
return NULL;
}
else if (root->left == NULL && root->right)
{
//左为空,右不为空
return root->right;
delete root;
}
else if (root->right == NULL && root->left)
{
//左不为空,右为空
return root->left;
delete root;
}
else if (root->right && root->left)
{
//左右都不为空,最复杂
//将左子树放右子树里最小的值的左孩子上
TreeNode* cur = root->right;
//不断遍历,找到右子树最左边的值
while (cur&&cur->left)
{
cur = cur->left;
}
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;
}
};
int main()
{
TreeNode* node3 = new TreeNode(2);
TreeNode* node4 = new TreeNode(4);
TreeNode* node5 = new TreeNode(7);
TreeNode* node1 = new TreeNode(3,node3,node4);
TreeNode* node2 = new TreeNode(6,NULL,node5);
TreeNode* root = new TreeNode(5, node1, node2);
Solution s;
s.deleteNode(root, 3);
return 0;
}
运行如下: