十、创建二叉树
1.套路
1.大多数题给定数组构造二叉树 ,本质上是通过查找(可能是二分)获取当前根节点下标/或用哈希表提前存(比如已知前序/中序/后序中的两个构造二叉树),然后左侧数组递归,右侧数组递归,所以递归参数传递当前数组左右边界
2.题目描述
3.学习经验
1. 108. 将有序数组转换为二叉搜索树(简单,学习)
108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)
思想
1.给你一个整数数组 nums
,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。
2.用左右边界递归,而不是把左数组和右数组作为参数传递
代码
class Solution {
public:
TreeNode* build(vector<int>& nums, int l, int r) {
// [l,r]
if (r < l)
return nullptr;
int mid = l + ((r - l) >> 1);
// [l,mid),[mid+1,r]
return new TreeNode(nums[mid], build(nums, l, mid - 1),
build(nums, mid + 1, r));
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
return build(nums, 0, nums.size() - 1);
}
};
2. 654. 最大二叉树(中等)
思想
1.给定一个不重复的整数数组 nums
。 最大二叉树 可以用下面的算法从 nums
递归地构建:
- 创建一个根节点,其值为
nums
中的最大值。 - 递归地在最大值 左边 的 子数组前缀上 构建左子树。
- 递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回nums
构建的 最大二叉树 。
代码
class Solution {
public:
TreeNode* bulid(vector<int>& nums,int l,int r){
// [l,r]
if(r<l) return nullptr;
int maxId=l;
for(int i=l;i<=r;++i){
if(nums[i]>nums[maxId]) maxId=i;
}
// [l,maxId),[maxId+1,r]
return new TreeNode(nums[maxId],
bulid(nums,l,maxId-1),
bulid(nums,maxId+1,r)
);
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
return bulid(nums,0,nums.size()-1);
}
};
3. 998. 最大二叉树II(中等)
思想
1.最大树 定义:一棵树,并满足:其中每个节点的值都大于其子树中的任何其他值。
给你最大树的根节点 root
和一个整数 val
。
就像 之前的问题 那样,给定的树是利用 Construct(a)
例程从列表 a
(root = Construct(a)
)递归地构建的:
- 如果
a
为空,返回null
。 - 否则,令
a[i]
作为a
的最大元素。创建一个值为a[i]
的根节点root
。 root
的左子树将被构建为Construct([a[0], a[1], ..., a[i - 1]])
。root
的右子树将被构建为Construct([a[i + 1], a[i + 2], ..., a[a.length - 1]])
。- 返回
root
。
请注意,题目没有直接给出a
,只是给出一个根节点root = Construct(a)
。
假设b
是a
的副本,并在末尾附加值val
。题目数据保证b
中的值互不相同。
返回Construct(b)
。
2.由例子可以看出:
a = [2,1,5,4], b = [2,1,5,4,3]
就是在数组后面插入一个数val
,然后来更新树,再由最大树构造性质,只需判断根节点和右子树
代码
class Solution {
public:
TreeNode* insertIntoMaxTree(TreeNode* root, int val) {
if (root == nullptr)
return new TreeNode(val);
if (root->val < val) {
return new TreeNode(val, root, nullptr);
}
TreeNode* newR = insertIntoMaxTree(root->right, val);
root->right = newR; // 更新右子树
return root;
}
};
4. 1008. 前序遍历构造二叉搜索树(中等)
1008. 前序遍历构造二叉搜索树 - 力扣(LeetCode)
思想
代码
class Solution {
public:
TreeNode* build(vector<int>& preorder, int l, int r) {
// [l,r]
if (l > r)
return nullptr;
int tmpL = l, tmpR = r, lEnd = r + 1;
// 查找
while (tmpL <= tmpR) {
int mid = tmpL + ((tmpR - tmpL) >> 1);
if (preorder[mid] > preorder[l]) {
lEnd = mid;
tmpR = mid - 1;
} else
tmpL = mid + 1;
}
// [l+1,lEnd),[lEnd,r]
return new TreeNode(preorder[l], build(preorder, l + 1, lEnd - 1),
build(preorder, lEnd, r));
}
TreeNode* bstFromPreorder(vector<int>& preorder) {
return build(preorder, 0, preorder.size() - 1);
}
};
5. 1382. 将二叉搜索树变平衡(中等)
1382. 将二叉搜索树变平衡 - 力扣(LeetCode)
思想
1.给你一棵二叉搜索树,请你返回一棵 平衡后 的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。如果有多种构造方法,请你返回任意一种。
如果一棵二叉搜索树中,每个节点的两棵子树高度差不超过 1
,我们就称这棵二叉搜索树是 平衡的 。
2.先中序遍历二叉搜索树得到数组,然后再用数组构造平衡二叉搜索树
代码
class Solution {
public:
vector<int> nums;
void dfs(TreeNode* cur) {
if (cur == nullptr)
return;
dfs(cur->left);
nums.push_back(cur->val);
dfs(cur->right);
}
TreeNode* build(vector<int>& nums, int l, int r) {
// [l.r]
if (l > r)
return nullptr;
int mid = l + ((r - l) >> 1);
return new TreeNode(nums[mid], build(nums, l, mid - 1),
build(nums, mid + 1, r));
}
TreeNode* balanceBST(TreeNode* root) {
dfs(root);
return build(nums, 0, nums.size() - 1);
}
};
6. 2196. 根据描述创建二叉树(中等,学习)
2196. 根据描述创建二叉树 - 力扣(LeetCode)
思想
1.给你一个二维整数数组 descriptions
,其中 descriptions[i] = [parenti, childi, isLefti]
表示 parenti
是 childi
在 二叉树 中的 父节点 ,二叉树中各节点的值 互不相同 。此外:
- 如果
isLefti == 1
,那么childi
就是parenti
的左子节点。 - 如果
isLefti == 0
,那么childi
就是parenti
的右子节点。
请你根据descriptions
的描述来构造二叉树并返回其 根节点 。
测试用例会保证可以构造出 有效 的二叉树。
2.本题就是建树+找根,建树没有问题,找根用的并查集,其实没必要,根节点入度为0(重要性质),所以建树时记录入度
代码
我的代码:
class Solution {
public:
map<int, TreeNode*> valToPoint;
map<int, int> valToFa;
int find(int x) {
if (x != valToFa[x])
valToFa[x] = find(valToFa[x]);
return valToFa[x];
}
void merge(int par, int chi) {
int parFa = find(par), parChi = find(chi);
if (parFa == parChi)
return;
valToFa[parChi] = parFa;
}
TreeNode* createBinaryTree(vector<vector<int>>& descriptions) {
TreeNode* res;
for (auto& des : descriptions) {
int par = des[0], chi = des[1], isL = des[2];
TreeNode *parPoint = nullptr, *chiPoint = nullptr;
auto isPar = valToPoint.find(par), isChil = valToPoint.find(chi);
if (isPar != valToPoint.end()) {
parPoint = valToPoint[par];
} else {
parPoint = new TreeNode(par);
valToPoint[par] = parPoint;
valToFa[par] = par;
}
if (isChil != valToPoint.end()) {
chiPoint = valToPoint[chi];
} else {
chiPoint = new TreeNode(chi);
valToPoint[chi] = chiPoint;
valToFa[chi] = chi;
}
if (isL)
parPoint->left = chiPoint;
else
parPoint->right = chiPoint;
merge(par, chi);
}
return valToPoint[find(descriptions[0][0])];
}
};
入度:
class Solution {
public:
map<int, TreeNode*> valToPoint;
map<int, int> inDeg;
TreeNode* createBinaryTree(vector<vector<int>>& descriptions) {
TreeNode* res;
for (auto& des : descriptions) {
int par = des[0], chi = des[1], isL = des[2];
if (!valToPoint.count(par)) {
valToPoint[par] = new TreeNode(par);
}
if (!valToPoint.count(chi)) {
valToPoint[chi] = new TreeNode(chi);
}
if (isL)
valToPoint[par]->left = valToPoint[chi];
else
valToPoint[par]->right = valToPoint[chi];
++inDeg[chi];
}
for (auto& x : valToPoint) {
int val = x.first;
TreeNode* point = x.second;
if (inDeg[val] == 0)
return point;
}
return nullptr;
}
};
7. 105. 从前序与中序遍历序列构造二叉树(中等)
105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)
思想
1.给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历 , inorder
是同一棵树的中序遍历 ,请构造二叉树并返回其根节点。
2.查找获取当前根节点下标需要前序数组,根节点值就是当前遍历到的前序数组值
3.优化:此题查找本身是给定值找下标,且一定存在,则预先通过哈希表预处理,每次递归查找时间从O(n)变成O(1)
代码
class Solution {
public:
int preId=0,n;
TreeNode* build(vector<int>& preorder,vector<int>& inorder,int inL,int inR){
// [inL,inR]
if(inR<inL || preId>=n) return nullptr;
int target=preorder[preId++];
int tmpRes=inL;
while(tmpRes<=inR){
if(inorder[tmpRes]==target) break;
++tmpRes;
}
return new TreeNode(target,build(preorder,inorder,inL,tmpRes-1),build(preorder,inorder,tmpRes+1,inR));
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
n=preorder.size();
return build(preorder,inorder,0,n-1);
}
};
哈希表预处理:
class Solution {
public:
int preId = 0, n;
map<int, int> valToId;
TreeNode* build(vector<int>& preorder, vector<int>& inorder, int inL,
int inR) {
// [inL,inR]
if (inR < inL || preId >= n)
return nullptr;
int target = preorder[preId++];
int tmpRes = valToId[target];
return new TreeNode(target, build(preorder, inorder, inL, tmpRes - 1),
build(preorder, inorder, tmpRes + 1, inR));
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
n = preorder.size();
for (int i = 0; i < n; ++i)
valToId[inorder[i]] = i;
return build(preorder, inorder, 0, n - 1);
}
};
8. 106. 从中序与后序遍历序列构造二叉树(中等)
106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)
思想
1.给定两个整数数组 inorder
和 postorder
,其中 inorder
是二叉树的中序遍历, postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
2.与[[十五.二叉树#7. 105. 从前序与中序遍历序列构造二叉树(中等)]]不同,此题后序数组得从后往前遍历,然后利用中序数组构造得先构造右子树,再构造左子树
代码
class Solution {
public:
map<int, int> valToId;
int postId, n;
TreeNode* build(vector<int>& inorder, vector<int>& postorder, int inL,
int inR) {
// [inL,inR]
if (inL > inR)
return nullptr;
int target = postorder[postId--];
int targetId = valToId[target];
TreeNode* r = build(inorder, postorder, targetId + 1, inR);
TreeNode* l = build(inorder, postorder, inL, targetId - 1);
return new TreeNode(target, l, r);
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
n = inorder.size();
postId = n - 1;
for (int i = 0; i < n; ++i)
valToId[inorder[i]] = i;
return build(inorder, postorder, 0, n - 1);
}
};
9. 889. 根据前序和后序遍历构造二叉树(中等)
889. 根据前序和后序遍历构造二叉树 - 力扣(LeetCode)
思想
1.给定两个整数数组,preorder
和 postorder
,其中 preorder
是一个具有 无重复 值的二叉树的前序遍历,postorder
是同一棵树的后序遍历,重构并返回二叉树。
如果存在多个答案,您可以返回其中 任何 一个。
代码
class Solution {
public:
int preId, postId, n;
map<int, int> valToId;
TreeNode* build(vector<int>& preorder, vector<int>& postorder, int postL,
int postR) {
// [postL,postR]
if (postL > postR)
return nullptr;
++preId;
if (postL == postR) {
return new TreeNode(postorder[postL]);
}
int target = preorder[preId];
int targetId = valToId[target];
// [postL,targetId]:left,[targetId+1,postR-1]:right
return new TreeNode(
postorder[postR], build(preorder, postorder, postL, targetId),
build(preorder, postorder, targetId + 1, postR - 1));
}
TreeNode* constructFromPrePost(vector<int>& preorder,
vector<int>& postorder) {
n = preorder.size();
preId = 0, postId = n - 1;
for (int i = 0; i < n; ++i)
valToId[postorder[i]] = i;
return build(preorder, postorder, 0, n - 1);
}
};
十一、插入/删除节点
1.套路
1.插入/删除节点都要改变当前节点的左子树或右子树,所以递归返回值是TreeNode*
,要赋值给root->left,root->right
2.题目描述
3.学习经验
1. 701. 二次搜索树中的插入操作(中等)
701. 二叉搜索树中的插入操作 - 力扣(LeetCode)
思想
1.给定二叉搜索树(BST)的根节点 root
和要插入树中的值 value
,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意 ,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
代码
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 if (root->val > val)
root->left = insertIntoBST(root->left, val);
return root;
}
};
2. 450. 删除二次搜索树中的节点(中等,学习)
450. 删除二叉搜索树中的节点 - 力扣(LeetCode)
思想
1.给定一个二叉搜索树的根节点 root 和一个值 key ,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
2.思路为:
根据二叉搜索树的性质:
- 如果目标节点大于当前节点值,则去右子树中删除;
- 如果目标节点小于当前节点值,则去左子树中删除;
- 如果目标节点就是当前节点,分为以下三种情况:
- 其无左子:其右子顶替其位置,删除了该节点;
- 其无右子:其左子顶替其位置,删除了该节点;
- 其左右子节点都有:其左子树转移到其右子树的最左节点的左子树上,然后右子树顶替其位置,由此删除了该节点。(最难的 )(如下图所示)
![[450. 删除二次搜索树中的节点.png]]
代码
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == nullptr)
return nullptr;
if (root->val > key) { // 去左子树删除,左子树改变
root->left = deleteNode(root->left, key);
} else if (root->val < key) { // 去右子树删除,右子树改变
root->right = deleteNode(root->right, key);
} else { // 删当前节点
if (root->left == nullptr) { // 左子树为空,右子树替代
return root->right;
}
if (root->right == nullptr) { // 右子树为空,左子树替代
return root->left;
}
TreeNode* tmp = root->right;
// 找右子树最左节点
while (tmp->left != nullptr)
tmp = tmp->left;
tmp->left = root->left;
root = root->right;
}
return root;
}
};
3. 669. 修剪二叉搜索树(中等)
思想
1.给你二叉搜索树的根节点 root
,同时给定最小边界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->left = nullptr; // 抛弃左
root->right = trimBST(root->right, low, high);
// 修剪右
root = root->right; // 赋值右
} else if (root->val > high) {
root->right = nullptr; // 抛弃右
root->left = trimBST(root->left, low, high);
// 修剪左
root = root->left; // 赋值左
} else {
root->left = trimBST(root->left, low, high);
// 修剪左
root->right = trimBST(root->right, low, high);
// 修剪右
}
return root;
}
};
更简单一点:
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;
}
};