目录
修建二叉搜索树
题干
思路和代码
递归法
迭代法
将有序数组转化为平衡二叉搜索树
题干
思路和代码
递归法
递归优化
迭代法
把二叉搜索树转换为累加树
题干
思路和代码
递归法
迭代法
修建二叉搜索树
题干
题目:给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
说明:
-
每个结点值非负数,0 <= Node.val <= 104
-
树中每个节点的值都是 唯一 的
链接:. - 力扣(LeetCode)
思路和代码
先判断根节点是否在 [low, high] 的范围内,如果不是则删除,并从左右子树中找到新的根节点;如果是则递归修剪左右子树。
递归法
-
递归参数和返回值:参数是传入的根节点,闭区间的最小边界 low、最大边界 high;返回值是修建过的新子树的根节点。
-
递归结束的条件:当传入的节点为空,代表没有子树需要修建,返回空。
-
递归顺序:前序遍历,先判断根节点是否在 [low, high] 的范围内,如果不是则删除,并从左右子树中找到新的根节点;如果是则递归修剪左右子树。
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if (root == nullptr) return nullptr;
if (root->val < low){
// 根结点包括根节点的左子树都要被删除,新的根节点应为修剪过的右子树
root = trimBST(root->right,low,high);
} else if (root->val > high){
// 根结点包括根节点的右子树都要被删除,新的根节点应为修剪过的左子树
root = trimBST(root->left,low,high);
} else{
// 根节点在 [low, high] 范围内,修剪左右子树
if (root->left) root->left = trimBST(root->left,low,high);
if (root->right) root->right = trimBST(root->right,low,high);
}
return root;
}
};
迭代法
迭代法暂时不太理解思路,需要二刷的时候复盘。
将有序数组转化为平衡二叉搜索树
题干
题目:给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵平衡二叉搜索树。
链接:. - 力扣(LeetCode)
思路和代码
要让二叉搜索树平衡,也就是让左右子树的高度差尽量相同,即让左右子树的节点数目尽量相同。那么只需每次建树时让序列中点作为根节点,再让左右子树建树。
递归法
-
递归参数和返回值:参数即传入的序列数组,返回值是根据序列构建好的二叉树的根节点,需要将返回值传递给上层父节点。
-
递归结束的条件:当传入的序列数组为空,返回空节点。
-
递归顺序:前序遍历,每次让序列中点作为根节点,再递归构建左右子树。
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
int size = nums.size();
if (size == 0) return nullptr;
TreeNode* root = new TreeNode(nums[size/2]); // 让序列中点为根节点
vector<int> leftTree(nums.begin(),nums.begin()+size/2); // 左子树序列
root->left = sortedArrayToBST(leftTree); // 递归建立左子树
vector<int> rightTree(nums.begin()+size/2+1,nums.end()); // 右子树序列
root->right = sortedArrayToBST(rightTree); // 递归建立右子树
return root;
}
};
递归优化
在上一个方法中我们新建了左子树和右子树的序列数组,但其实只需要知道左子树和右子树的序列下标即可。因此在这里我们的递归参数需要传递序列下标。
-
递归参数和返回值:参数是原始的序列数组、要构建子树的序列下标 low、high;返回值是根据序列构建好的二叉树的根节点。
-
递归结束的条件:当传入的序列数组为空,返回空节点。
-
递归顺序:前序遍历,每次让序列中点作为根节点,再递归构建左右子树。
class Solution {
public:
// 左闭右闭
TreeNode* buildTree(vector<int>& nums, int low, int high){
if (high < low) return nullptr; // 序列为空,返回空节点
int mid = (low+high)/2; // 记录中点位置
TreeNode* root = new TreeNode(nums[mid]); // 让中点为根节点
root->left = buildTree(nums,low,mid-1); // 建立左子树
root->right = buildTree(nums,mid+1,high); // 建立右子树
return root;
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
return buildTree(nums,0,nums.size()-1);
}
};
迭代法
在递归法中我们每次都先根据序列中点建立根节点,再以中点划分左右子树,获取左右子树的区间下标。在迭代法中,我们采取相同思路,用三个队列分别存储新建的根节点、左子树区间、右子树区间。每次都从根节点序列中取出根节点,令根节点的左孩子 = 左子树区间中点,根节点的右孩子 = 右子树区间的中点,再将根节点的左右孩子插入节点队列中,如此循环。
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
int size = nums.size();
TreeNode* root = new TreeNode(nums[size/2]); // 建立根节点
queue<TreeNode*> nodes; // 记录建立的节点
nodes.push(root); // 初始为根节点
queue<vector<int>> leftTree; // 记录左子树的下标范围
leftTree.push({0,size/2-1});
queue<vector<int>> rightTree; // 记录右子树的下标范围
rightTree.push({size/2+1,size-1});
while (!nodes.empty()){
TreeNode* father = nodes.front(); nodes.pop(); // 记录父节点
// 取出左子树区间
int leftLow = leftTree.front()[0];
int leftHigh = leftTree.front()[1];
int leftMid = (leftLow+leftHigh)/2;
leftTree.pop();
// 当区间有效时,建立左子树
if (leftLow <= leftHigh) father->left = new TreeNode(nums[leftMid]);
// 取出右子树区间
int rightLow = rightTree.front()[0];
int rightHigh = rightTree.front()[1];
int rightMid = (rightLow+rightHigh)/2;
rightTree.pop();
// 当区间有效时,建立右子树
if (rightLow <= rightHigh) father->right = new TreeNode(nums[rightMid]);
// 继续划分左右子树
if (leftLow < leftHigh){
nodes.push(father->left); // 加入左子树根节点
// 以左子树为根节点划分左右子树
leftTree.push({leftLow,leftMid-1});
rightTree.push({leftMid+1,leftHigh});
}
if (rightLow < rightHigh){
nodes.push(father->right); // 加入右子树根节点
// 以右子树为根节点划分左右子树
leftTree.push({rightLow,rightMid-1});
rightTree.push({rightMid+1,rightHigh});
}
}
return root;
}
};
把二叉搜索树转换为累加树
题干
题目:给出二叉搜索树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值 等于 原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
-
节点的左子树仅包含键 小于 节点键的节点。
-
节点的右子树仅包含键 大于 节点键的节点。
-
左右子树也必须是二叉搜索树。
链接:. - 力扣(LeetCode)
思路和代码
由于是二叉搜索树,则 大于等于原节点的值 肯定都在该节点自身及其右子树中。累加的时候可以根据 “右中左” 的顺序遍历二叉树,二叉树的最右节点肯定是二叉树的最大值,没有比他更大的,该节点作为起点,之后再遍历中间节点、左子树。这样每个节点的新值 = 上一个节点的值 + 自己原来的值。
递归法
-
递归参数和返回值:参数即传入的根节点,无返回值,在递归过程中一步步修改节点值。
-
递归结束的条件:当传入的节点为空,返回空。
-
递归顺序:根据 ”右中左“ 的顺序遍历二叉树,先递归右子树,修改中间节点,再递归左子树。
class Solution {
public:
TreeNode* pre = nullptr; // 记录 遍历过的上一个节点
void sum(TreeNode* root){ // 计算累加和
if (root == nullptr) return;
sum(root->right); // 先遍历右子树
if (pre != nullptr){
root->val += pre->val; // 累加节点值
}
pre = root;
sum(root->left); // 遍历左子树
}
TreeNode* convertBST(TreeNode* root) {
sum(root);
return root;
}
};
迭代法
思路和之前的方法相同,只不过把反中序递归改为反中序迭代遍历,属于模板题。
class Solution {
public:
TreeNode* convertBST(TreeNode* root) {
TreeNode* cur = root;
stack<TreeNode*> tmpNode;
TreeNode* pre = nullptr; // 记录 cur 的上一个节点
while (cur != nullptr || !tmpNode.empty()){
if (cur != nullptr){
tmpNode.push(cur);
cur = cur->right; // 一直遍历右子树直到找到最右节点
} else{
// 找到了最右节点
cur = tmpNode.top();
tmpNode.pop();
if (pre != nullptr){
cur->val += pre->val; // 修改节点值
}
pre = cur;
cur = cur->left; // 遍历左子树
}
}
return root;
}
};