235. 二叉搜索树的最近公共祖先
解题思路总结
本题利用二叉搜索树的有序性寻找最近公共祖先。当当前节点的值同时大于目标节点
p和q的值时,说明二者均位于左子树,应向左继续搜索;当当前节点的值同时小于p和q的值时,说明二者均位于右子树,应向右继续搜索;否则当前节点即为两节点的最近公共祖先。
思路展开说明
关键在于 BST 的路径唯一性:
-
在 BST 中:
- 从根节点到任意节点的路径是唯一的
-
对于
p和q:-
它们的最近公共祖先
-
一定是路径第一次发生分叉的那个节点
-
判断分叉的位置,只需要比较数值大小关系即可。
cpp
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;
}
};
701. 二叉搜索树中的插入操作
解题思路总结
本题利用二叉搜索树的有序性完成节点插入操作。从根节点开始,根据待插入值与当前节点值的大小关系,选择向左或向右子树不断向下搜索,直到遇到空位置,再将新节点插入到该位置,从而保持二叉搜索树的结构与有序性。
思路展开说明
插入 BST 的本质是一次有方向的查找:
-
BST 的基本性质
-
左子树 < 根节点
-
右子树 > 根节点
-
-
插入原则
-
插入过程一定沿着一条路径向下
-
最终插入到某个叶子节点的空孩子位置
-
-
实现方式
-
既可以递归
-
也可以用迭代(本题采用迭代,更直观、节省栈空间)
-
cpp
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(root==NULL) {
TreeNode* node = new TreeNode(val);
return node;
}
TreeNode* cur = root;
TreeNode* pre = root;
while(cur){
pre = cur;
if(cur->val>val)cur = cur->left;
else cur = cur->right;
}
TreeNode* node = new TreeNode(val);
if(pre->val>val) pre->left=node;
else pre->right=node;
return root;
}
};
450. 删除二叉搜索树中的节点
解题思路总结
本题基于二叉搜索树的有序性递归删除指定节点。首先沿着 BST 的查找路径定位目标节点;当找到待删除节点时,根据其左右子树情况分别处理:若为叶子节点直接删除;若仅有一个子节点则用该子节点替换;若左右子节点均存在,则以右子树的最小节点作为替代节点,将左子树接入该节点后删除原节点,从而保持二叉搜索树的结构与有序性。
思路展开说明
1️⃣ 删除 BST 节点的整体流程
-
先找到节点
-
利用 BST 性质:
-
小 → 左
-
大 → 右
-
-
-
找到后分情况处理
-
情况一:无子节点(叶子)
-
情况二:只有左子树
-
情况三:只有右子树
-
情况四:左右子树都存在(最复杂)
-
2️⃣ 四种情况的核心处理思想
| 情况 | 处理方式 |
|---|---|
| 叶子节点 | 直接删除,返回 nullptr |
| 只有左子树 | 用左子树替代当前节点 |
| 只有右子树 | 用右子树替代当前节点 |
| 左右子树都有 | 找右子树最小节点作为接管者 |
为什么选 右子树最小节点?
-
它是中序遍历中"紧接当前节点"的后继
-
能保证 BST 的有序性不被破坏
cpp
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == nullptr) return root; // 第一种情况:没找到删除的节点,遍历到空节点直接返回了
if (root->val == key) {
// 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
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的左孩子的位置
TreeNode* tmp = root; // 把root节点保存一下,下面来删除
root = root->right; // 返回旧root的右孩子作为新root
delete tmp; // 释放节点内存(这里不写也可以,但C++最好手动释放一下吧)
return root;
}
}
if (root->val > key) root->left = deleteNode(root->left, key);
if (root->val < key) root->right = deleteNode(root->right, key);
return root;
}
};
669. 修剪二叉搜索树
解题思路总结
本题利用二叉搜索树的有序性递归修剪节点。若当前节点值小于区间下界,则其左子树必然不满足条件,直接递归处理右子树;若当前节点值大于区间上界,则其右子树必然不满足条件,直接递归处理左子树;当当前节点值位于区间
[low, high]内时,递归修剪其左右子树,并将修剪后的结果重新接入当前节点,从而得到满足区间约束的二叉搜索树。
思路展开说明
1️⃣ 为什么 BST 能被"直接剪掉一整棵子树"
利用 BST 的性质:
-
若
root->val < low-
左子树所有节点值 一定 < low
-
左子树整体无效,直接丢弃
-
-
若
root->val > high-
右子树所有节点值 一定 > high
-
右子树整体无效,直接丢弃
-
👉 这也是本题效率高的根本原因。
2️⃣ 三种核心情况总结
| 当前节点值 | 处理方式 |
|---|---|
< low |
返回修剪后的右子树 |
> high |
返回修剪后的左子树 |
[low, high] |
保留节点,递归修剪左右子树 |
cpp
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);
if(root->val > high)return trimBST(root->left,low,high);
root->left = trimBST(root->left, low, high); // root->left接入符合条件的左孩子
root->right = trimBST(root->right, low, high); // root->right接入符合条件的右孩子
return root;
}
};
108. 将有序数组转换为二叉搜索树
解题思路总结
本题通过将有序数组不断按中点划分的方式构造高度平衡的二叉搜索树。每次选取当前区间的中间元素作为子树根节点,以保证左右子树规模尽量相等;随后分别对左右子区间继续构造子树。该实现采用层序遍历配合区间边界记录,等价于递归分治构建过程。
思路展开说明(不看代码也能理解)
1️⃣ 为什么选"中间元素"作为根
-
数组是升序
-
中点元素作为根:
-
左边都比它小 → 左子树
-
右边都比它大 → 右子树
-
-
左右子树规模接近 → 高度平衡
2️⃣ 递归思路 & 你这里的迭代等价形式
常见递归思路是:
cpp
build(left, right):
mid = (left + right) / 2
root = nums[mid]
root->left = build(left, mid-1)
root->right = build(mid+1, right)
做法是:
-
用 队列 保存:
-
当前要处理的节点
-
对应的数组区间
[left, right]
-
-
用 while 循环模拟递归展开
👉 本质仍然是 分治 + 中点建树
cpp
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
if(nums.size()==0)return nullptr;
TreeNode* root = new TreeNode(0);
queue<TreeNode*>nodeQue;
queue<int>nodeleft;
queue<int>noderight;
nodeQue.push(root);
nodeleft.push(0);
noderight.push(nums.size()-1);
while(!nodeQue.empty()){
TreeNode* curnode = nodeQue.front();
nodeQue.pop();
int left = nodeleft.front();nodeleft.pop();
int right = noderight.front();noderight.pop();
int mid = left+(right-left)/2;
curnode->val = nums[mid];
if(left<=mid-1){
curnode->left = new TreeNode(0);
nodeQue.push(curnode->left);
nodeleft.push(left);
noderight.push(mid-1);
}
if(mid+1<=right){
curnode->right = new TreeNode(0);
nodeQue.push(curnode->right);
nodeleft.push(mid+1);
noderight.push(right);
}
}
return root;
}
};
538. 把二叉搜索树转换为累加树
解题思路总结
本题利用二叉搜索树的有序性,通过反中序遍历(右 → 根 → 左)按从大到小的顺序访问节点,并在遍历过程中维护一个累加变量,将当前节点值更新为所有大于等于它的节点值之和,从而将原二叉搜索树转换为累加树。
思路展开说明
1️⃣ 关键观察:BST 的中序性质
-
中序遍历(左 → 根 → 右)
→ 节点值从小到大
-
反中序遍历(右 → 根 → 左)
→ 节点值从大到小
2️⃣ 为什么用反中序遍历
题目要求的是:
每个节点的新值 = 原值 + 所有 比它大的节点值之和
因此最自然的方式是:
-
先访问最大的节点
-
用一个变量
pre记录"已经遍历过的节点值之和" -
每访问一个节点:
-
当前值 += pre -
更新
pre
-
3️⃣ 实现方式选择
-
可以递归(更直观)
-
也可以迭代(你这里的做法):
-
用栈模拟递归
-
避免递归栈开销
-
cpp
class Solution {
public:
int pre = 0; // 记录前一个节点的数值
void traversal(TreeNode* root){
stack<TreeNode*>st;
TreeNode* cur = root;
while(!st.empty()||cur!=nullptr){
if (cur != NULL) {
st.push(cur);
cur = cur->right; // 右
}
else{
cur = st.top();
st.pop();
cur->val+=pre;
pre = cur->val;
cur= cur->left;
}
}
}
TreeNode* convertBST(TreeNode* root) {
pre = 0;
traversal(root);
return root;
}
};