235.二叉搜索树的最近公共祖先
本题用Day21最后一道题的代码,能够ac
/**
* 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 * res;
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
traversal(root, p, q);
return res;
}
bool traversal(TreeNode * root, TreeNode * p, TreeNode * q) {
if (root == nullptr) return false;
bool left = traversal(root -> left, p, q);
bool right = traversal(root -> right, p, q);
if (left && right) res = root;
if ((root -> val == p -> val || root -> val == q -> val) && (left || right)) res = root;
if (root -> val == p -> val || root -> val == q -> val) return true;
return left || right;
}
};
但是这样写就又没有用到二叉搜索树的条件
根据二叉搜索树的特性,从根节点往下遍历,若第一次遇到了数值在[p, q]区间中的节点,则一定可以说明该节点就是 q 和 p 的最近公共祖先
知道了这个之后,再开始利用递归三部曲处理该问题。定义递归函数的功能:在一棵二叉搜索树中寻找两个指定节点的最近公共祖先,并返回
确定参数和返回值 :参数需要传入一棵树,以及两个指定节点,返回一个公共祖先
确定终止条件:
如果为空树,显然没有指定节点的公共祖先,返回nullptr
if (root == nullptr) return nullptr;
如果该节点就是公共祖先,直接返回该节点,就不需要继续向深处搜索了(这样能够保证最终递归函数返回的节点是“第一次遇到的数值在指定节点区间中的节点”)
if (root -> val >= p -> val && root -> val <= q -> val) return root;
if (root -> val <= p -> val && root -> val >= q -> val) return root;
单层递归逻辑:需要根据根节点值于p和q值的大小关系,继续在左子树或者右子树寻找最近公共祖先。在子树中寻找最近公共祖先的操作能够通过递归函数本身实现
if (root -> val < p -> val && root -> val < q -> val) // 说明最近公共祖先要在右子树寻找
return lowestCommonAncestor(root -> right, p, q);
else // 说明最近公共祖先要在左子树寻找
return lowestCommonAncestor(root -> left, p, q);
整体代码
/**
* 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* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr) return nullptr;
if (root -> val >= p -> val && root -> val <= q -> val) return root;
if (root -> val <= p -> val && root -> val >= q -> val) return root;
if (root -> val < p -> val && root -> val < q -> val)
return lowestCommonAncestor(root -> right, p, q);
else
return lowestCommonAncestor(root -> left, p, q);
}
};
701.二叉搜索树中的插入操作
理解一点:在二叉搜索树中插入节点,只要遍历二叉搜索树,找到空节点插入元素就可以了,在叶子位置插入即可
定义递归函数功能:在二叉搜索树中寻找空位置并插入节点,然后返回插入后的二叉搜索树
确定递归函数参数和返回值:需要指定待插入的树和节点,返回一棵树
TreeNode* insertIntoBST(TreeNode* root, int val)
确定终止条件:如果是空树,在空树中插入节点就是直接创建一个节点,并返回该节点,此时这个节点可以看成一棵树
if (root == nullptr) {
TreeNode * node = new TreeNode(val);
return node;
}
确定单层递归逻辑:如果不是空树,需要决定应该在该树的左子树还是右子树插入,并在左子树或者右子树进行寻找空位并插入的操作。在子树中寻找空位并插入的操作能够通过递归函数本身实现
if (val > root -> val) // 应该在左子树中寻找空位并插入
root -> right = insertIntoBST(root -> right, val);
else if (val < root -> val) // 应该在右子树中寻找空位并插入
root -> left = insertIntoBST(root -> left, val);
注意:在某个子树进行插入操作后,返回插入新节点后的子树应该重新连接到根节点上
完整代码
/**
* Definition for a binary tree node.
* 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* insertIntoBST(TreeNode* root, int val) {
if (root == nullptr) {
TreeNode * node = new TreeNode(val);
return node;
}
if (val > root -> val)
root -> right = insertIntoBST(root -> right, val);
else if (val < root -> val)
root -> left = insertIntoBST(root -> left, val);
return root; // 别忘了我们的递归函数还需要返回插入后的树
}
};
450.删除二叉搜索树中的节点
题目链接/文章讲解/视频讲解
在二叉搜索树中删除节点后,维持删除后仍为二叉搜索树是关键
我们的基本思路还是递归三部曲
定义递归函数的功能:在树中找到对应的节点并删除,返回删除后的树
确定递归函数参数和返回值: 需要传入一棵树表示待操作的树,传入一个值表示待删除的节点,返回一个节点表示返回删除后的树
TreeNode* deleteNode(TreeNode* root, int key)
确定终止条件:
如果传入(遍历到)的是空树,返回肯定是 nullptr
如果传入(遍历到)的节点就是待删除节点,执行删除并返回删除后的树的根节点
确定单层递归逻辑: 在根节点的左子树或者右子树中寻找节点并删除,注意在子树中删除节点的操作能够通过递归函数本身实现。注意别忘了将删除节点后的子树重新连接到根节点上
/**
* Definition for a binary tree node.
* 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: //定义该函数作用是在一棵树中删除key,并返回根节点
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == nullptr) {
return nullptr;
}
if (root->val == key) {
// 传入节点就是待删除节点,执行删除节点并返回删除后的树的根节点
}
if (root->val > key) { // 在根节点的左子树中删除
root->left = deleteNode(root->left, key); // 递归调用获得了删除节点后的左子树,需要重新接到根节点上
}
if (root->val < key) {
root->right = deleteNode(root->right, key);
}
return root; // 别忘了返回删除了节点之后的树
}
};
代码框架写完了,目前就需要补充当遍历到的节点本身是待删除节点时,如何执行删除逻辑并返回
分为四种情况:
第一种情况:待删除节点的左右子树都为空,删除了这个节点后,剩下的树就是一棵空树,返回 nullptr
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)
{
auto retNode = root->left;
// 内存释放
delete root;
return retNode;
}
最后一种情况:待删除节点的左右子树都不为空,则删除节点后,将删除节点的左子树放到删除节点的右子树的最左下节点的左孩子的位置,并返回删除节点的右孩子为新的根节点
else {
TreeNode* cur = root->right; // 找右子树最左下的节点
while(cur->left != nullptr) {
cur = cur->left;
}
cur->left = root->left; // 把要删除的节点(root)左子树放在cur的左孩子的位置
auto retNode = root->right;
delete root;
return retNode;
}
完整代码
/**
* Definition for a binary tree node.
* 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: //定义该函数作用是在树中找到值为key的节点并删除,返回删除后的树
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == nullptr) {
return nullptr;
}
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)
{
auto retNode = root->left;
delete root;
return retNode;
}
else
{
TreeNode * cur = root->right;
while (cur->left != nullptr) cur = cur->left;
cur->left = root->left;
auto retNode = root->right;
delete root;
return retNode;
}
}
if (root->val > key) { // 在根节点的左子树中删除
root->left = deleteNode(root->left, key); // 递归调用获得了删除节点后的左子树,需要重新接到根节点上
}
if (root->val < key) {
root->right = deleteNode(root->right, key);
}
return root; // 别忘了返回删除了节点之后的树
}
};
回顾总结
今天的题目仍然逃不出递归三部曲的代码框架。关键是写代码的时候思路清晰,定义三部曲具体操作的时候一定要明确递归函数的功能是什么