每日算法刷题Day76:10.19:leetcode 二叉树12道题,用时3h
十、创建二叉树
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. 最大二叉树(中等)
654. 最大二叉树 - 力扣(LeetCode)
思想
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(中等)
998. 最大二叉树 II - 力扣(LeetCode)
思想
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;} elsetmpL = 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;elseparPoint->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];elsevalToPoint[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]:rightreturn 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. 修剪二叉搜索树(中等)
669. 修剪二叉搜索树 - 力扣(LeetCode)
思想
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;}
};