701m. 二叉搜索树中的插入操作
方法一:迭代法
用时:11m38s
思路
从根节点开始迭代,若要插入的元素大于(小于)当前节点,则要插入的位置在当前节点右边(左边),如果当前节点的右节点(左节点)不存在,则直接插入,如果存在,则当前节点迭代为右节点(左节点)。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
TreeNode* newNode = new TreeNode(val);
if (root == nullptr) return newNode;
TreeNode* cur = root;
while (true) {
if (cur->val < val) {
if (cur->right) cur = cur->right;
else {
cur->right = newNode;
break;
}
} else {
if (cur->left) cur = cur->left;
else {
cur->left = newNode;
break;
}
}
}
return root;
}
};
方法二:递归法
用时:7m17s
思路
本质上与方法一一致,使用递归的方法实现。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == nullptr) return new TreeNode(val);
if (root->val < val) root->right = insertIntoBST(root->right, val);
else root->left = insertIntoBST(root->left, val);
return root;
}
};
看完讲解的思考
代码实现遇到的问题
450m. 删除二叉搜索树中的节点
方法一:递归法
用时:1h10m58s
思路
首先分析清楚我们要如何删除一个节点,假设当前节点是cur
,当前节点的父节点是pre
,分情况讨论:
- 如果
cur
是叶子节点,则让pre
指向空节点,然后删除cur
。 - 如果
cur
只有一个子节点child
,则让pre
指向child
,然后删除cur
。 - 如果
cur
有两个子节点left
和right
,假设右子树最小的节点为rightMin
,让rightMin
的左指针指向left
,然后让pre
指向right
,最后删除cur
。
删除二叉树节点跟删除链表的节点一样,一定要先处理删除节点的前一个节点,然后再删除掉要删除的节点,如果直接删除节点而不处理pre,则pre的指针就会指向已经被释放的空间导致bug。
搞清楚删除的逻辑后再来处理代码实现的逻辑。第一种方法我们可以使用递归来实现,递归三部曲:
-
确定递归函数参数以及返回值。传入根节点和要删除的元素,返回根节点。
-
确定终止条件。遇到空返回,其实这也说明没找到删除的节点,递归到空节点直接返回了。
-
确定单层递归的逻辑,每次递归只判断当前节点:
- 如果当前节点的值不等于要删除的元素,则继续递归。
- 如果当前节点的值等于要删除的元素,则根据上述1、2、3点的删除逻辑删除当前节点。在递归中,返回的节点赋值给上一层递归的节点的子节点,其实就隐式的实现了处理pre的逻辑。
-
时间复杂度: O ( n ) O(n) O(n)。
-
空间复杂度: O ( n ) O(n) O(n)。时间复杂度和空间复杂度都为 O ( d ) O(d) O(d),其中 d d d为递归的深度,即树的高度,最坏情况下树为链状,则复杂度为 O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == nullptr) return nullptr; // 情况1:当前节点为空
if (root->val == key) {
TreeNode* node = nullptr; // 情况2:当前节点是叶子节点
if (root->left && root->right) { // 情况3:当前节点的左右节点都存在
TreeNode* rightMinNode = root->right;
while (rightMinNode->left) rightMinNode = rightMinNode->left;
rightMinNode->left = root->left;
node = root->right;
} else if (!(root->left == nullptr && root->right == nullptr)) node = root->left ? root->left : root->right; // 情况4:当前节点只存在一个子节点
delete root;
return node;
} else if (root->val > key) root->left = deleteNode(root->left, key);
else root->right = deleteNode(root->right, key);
return root;
}
};
方法二:迭代法
思路
迭代法实现的本质与递归法一致,主要是要理清楚删除节点的逻辑。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
TreeNode* cur = root;
TreeNode* pre = nullptr;
while (cur) {
if (cur->val == key) { // 如果当前节点的值等于要删除的值
TreeNode* node = nullptr; // 如果是叶子节点
if (cur->left && cur->right) { // 如果左右节点都存在
TreeNode* rightMin = cur->right;
while (rightMin->left) rightMin = rightMin->left;
rightMin->left = cur->left;
node = cur->right;
} else if (!(cur->left == nullptr && cur->right == nullptr)) node = cur->left ? cur->left : cur->right; // 如果只存在一个子节点
// 更新父节点的指向;如果要删除的节点是根节点,则修改根节点
if (pre) {
if (pre->left && pre->left->val == cur->val) pre->left = node;
else pre->right = node;
} else root = node;
// 删除当前节点并停止迭代
delete cur;
break;
} else { // 如果当前节点的值不等于要删除的值,则继续迭代
pre = cur;
cur = cur->val < key ? cur->right : cur->left;
}
}
return root;
}
};
看完讲解的思考
删除二叉树节点与删除链表节点的操作很类似,都是要先处理前一个指针的指向,再来删除要删除的节点。
代码实现遇到的问题
没有先处理父节点指针的指向,导致指针指向了被释放的空间。
669m. 修剪二叉搜索树
方法一:递归法
用时:42m38s
思路
如果当前节点的值小于下界,那么左子树肯定都小于下界,只用判断右子树;如果当前节点的值大于上界,那么右子树肯定都大于下界,只用判断左子树;如果当前节点的值位于范围内,则分别递归判断左右子树
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if (root == nullptr) return nullptr;
if (root->val < low) return trimBST(root->right, low, high); // 如果当前节点的值小于下界,那么左子树肯定都小于下界,只用判断右子树
else if (root->val > high) return trimBST(root->left, low, high); // 如果当前节点的值大于上界,那么右子树肯定都大于下界,只用判断左子树
else { // 如果当前节点的值位于范围内,则分别递归判断左右子树
root->left = trimBST(root->left, low, high);
root->right = trimBST(root->right, low, high);
return root;
}
}
};
方法二:迭代法
用时:18m4s
思路
首先找到第一个满足条件的节点作为根节点,然后分别修剪左子树和右子树。
修剪左子树的逻辑:如果当前节点cur
的左节点cur->left
存在,并且左节点的值小于下界,那么让cur->left = cur->left->right
,这样就相当于修剪掉了左节点以及左节点的左子树(左节点小于下界,那么左节点的左子树肯定也小于下界)。如果当前节点的左节点满足要求,则保留左节点,迭代当前节点,继续判断左节点。
修建右子树的逻辑与修建左子树的类似。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
// 找到满足条件的节点作为根节点
while (root && (root->val < low || root->val > high)) {
if (root->val < low) root = root->right;
else root = root->left;
}
// 修剪左子树
TreeNode* cur = root;
while (cur) {
if (cur->left && cur->left->val < low) cur->left = cur->left->right;
else cur = cur->left;
}
// 修剪右子树
cur = root;
while (cur) {
if (cur->right && cur->right->val > high) cur->right = cur->right->left;
else cur = cur->right;
}
return root;
}
};
看完讲解的思考
此题的修剪与上一题450m的删除不同,本题并没有真正的释放节点占用的内存空间,是不是内存泄漏了?
代码实现遇到的问题
无。
108e. 将有序数组转换为二叉搜索树
方法一:递归法
用时:5m20s
思路
每次取数组中间位置的元素作为根节点的值。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
private:
TreeNode* bulidBST(vector<int>& nums, int begin, int end) {
if (begin > end) return nullptr;
int mid = begin + (end - begin) / 2;
TreeNode* root = new TreeNode(nums[mid]);
root->left = bulidBST(nums, begin, mid - 1);
root->right = bulidBST(nums, mid + 1, end);
return root;
}
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
return bulidBST(nums, 0, nums.size() - 1);
}
};
方法二:迭代法
用时:
思路
- 时间复杂度: O ( ) O() O()
- 空间复杂度: O ( ) O() O()
C++代码
看完讲解的思考
代码实现遇到的问题
538m. 把二叉搜索树转换为累加树
方法一:反向中序遍历递归法
用时:3m41s
思路
BST中序遍历数组是递增数组,中序遍历顺序是“左中右”,那么反向中序遍历数组“右中左”就是递减数组,利用这一点我们就能由大到小遍历每一个元素,并且在遍历的过程中,用一个变量sum记录遍历过的元素之和,当遍历到某一个节点cur时,sum记录的就是比它大的元素之和,此时cur的值更新为cur->val+sum,即原树中大于等于cur->val的元素之和,然后更新sum。反向中序遍历的实现与正常的中序遍历一样,就是处理左右节点的顺序反过来。可以用递归或者迭代实现。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
private:
int sum;
public:
TreeNode* convertBST(TreeNode* root) {
if (root == nullptr) return nullptr;
root->right = convertBST(root->right); // 右
root->val += sum; // 中
sum = root->val;
root->left = convertBST(root->left); // 左
return root;
}
};
方法二:迭代法
思路
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
TreeNode* convertBST(TreeNode* root) {
int sum = 0;
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur || !st.empty()) {
if (cur) {
st.push(cur);
cur = cur->right;
} else {
cur = st.top();
st.pop();
cur->val += sum;
sum = cur->val;
cur = cur->left;
}
}
return root;
}
};
看完讲解的思考
无。
代码实现遇到的问题
中序遍历的迭代法还是不够熟练啊,居然还写不出来,得多练练。
最后的碎碎念
终于结束了二叉树专题了!!!这一章的内容是真多啊,最主要还是递归和迭代法,基本二叉树题目都是这样。