题源codetop标签近半年+树
- 1.二叉树的层序遍历
- 2.二叉树的层序遍历II
- 3.二叉树的锯齿形层次遍历
- 4.N叉树的层次遍历
- 5.二叉树的最近公共祖先
- 6.二叉搜索树的最近公共祖先
- 7.二叉树的直径
- 8.二叉树中最大路径和
- 9.二叉树的前序遍历
- 10.从前序与中序遍历序列构造二叉树
- 11.从中序与后序遍历序列构造二叉树
- 12.二叉树的右视图
- 13.二叉树最大宽度
- 14.二叉树的最大深度
- 15.N叉树的最大深度
- 16.二叉树的最小深度
- 17.子树的最大平均值
- 18.求根节点到叶节点的数字之和
- 19.另一棵树的子树
- 20.对称二叉树
- 21.子结构判断
- 22.寻找重复的子树
- 23.相同的树
- 24.平衡二叉树
- 25.二叉树展开为链表
- 26.将二叉搜索树转化为排序的双向链表
- 27.验证二叉搜索树
- 28.二叉树的完全性检验
- 29.完成二叉树的节点个数
- 30.删除二叉搜索树中的节点
- 31.寻找二叉树中的目标节点
- 32.二叉搜索树中第k小的元素
- 33.删除给定值的叶子节点
- 34.把二叉搜索树转换为累加树
- 35.合并二叉树
- 36.翻转二叉树
- 37.二叉树中所有距离为k的节点
- 38.路径总和II
- 39.寻找重复的子树
- 40.二叉树的序列化和反序列化
1.二叉树的层序遍历
层序遍历一般写法,通过一个while循环控制从上向下一层层遍历,for循坏控制每一层从左到右
class Solution{
public:
vector<vector<int>> levelOrder(TreeNode* root){
vector<vector<int>> res;
if(root == nullptr) return res;
queue<TreeNode*> q;
q.push(root);
// while循环控制从上到下
while(!q.empty()){
int size = q.size();
vector<int> level;
// for循环
for(int i = 0; i < sz; i ++){
TreeNode* cur = q.front();
q.pop();
level.push_back(cur->val);
if(cur->val != nullptr) q.push(cur->left);
if(cur->val != nullptr) q.push(cur->right);
}
res.push_back(level);
}
return res;
}
};
2.二叉树的层序遍历II
返回其节点值 自底向上 的层序遍历。
用双端队列存储
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
deque<vector<int>> res;
if(root == nullptr) return vector<vector<int>>(res.begin(), res.end());
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
int size = q.size();
vector<int> level;
for(int i = 0; i < size; i ++){
TreeNode* cur = q.front();
q.pop();
level.push_back(cur->val);
if(cur->left != nullptr) q.push(cur->left);
if(cur->right != nullptr) q.push(cur->right);
}
res.push_front(level);//重点!!!
}
return vector<vector<int>>(res.begin(), res.end());
}
};
3.二叉树的锯齿形层次遍历
给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
和层次遍历是一样的,只是多用一个flag来控制遍历方向。flag 为 true 时向右,false 时向左。
class Solution{
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root){
vector<vector<int>> res;
if(root == nullptr) return res;
queue<TreeNode*> q;
q.push(root);
bool flag = true;
while(!q.empty()){
int size = q.size();
deque<int> level;// 双端队列
for(int i = 0; i < size; i ++){
TreeNode* cur = q.front();
q.pop();
if(flag){
level.push_back(cur->val);
}else{
level.push_front(cur->val);
}
if(cur->left != nullptr) q.push(cur->left);
if(cur->right != nullptr) q.push(cur->right);
}
flag = !flag;// 一层结束切换方向
res.push_back(vector<int>(level.begin(), level.end()));
}
return res;
}
};
4.N叉树的层次遍历
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>> res;
if(root == nullptr) return res;
queue<Node*> q;
q.push(root);
while(!q.empty()){
int size = q.size();
vector<int> level;
for(int i = 0; i < size; i ++){
Node* cur = q.front();
q.pop();
level.push_back(cur->val);
for(Node* child : cur->children) q.push(child);
}
res.push_back(level);
}
return res;
}
};
5.二叉树的最近公共祖先
给该函数输入三个参数 root,p,q,它会返回一个节点:
- 如果p和q都在以root为根的树中,函数返回的是p和q的最近公共祖先节点
- 如果p和q都不在以root为根的树中,函数返回null
- 如果p和q只有一个存在于root为根的树中,函数会返回该节点
class Solution{
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q){
// base case
if(root == nullptr) return nullptr;
if(root == p || root == q) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if(left != nullptr && right != nullptr) return root;// 情况1
if(left == nullptr && right == nullptr) return nullptr;// 情况2
return left == nullptr ? right : left;
}
};
6.二叉搜索树的最近公共祖先
- 如果p和q都比当前节点小,那么p和q显然都在左子树
- 如果p和q都比当前节点大,那么p和q显然都在右子树
- 一旦发现p和q都在当前节点两侧,那么当前节点就是
class Solution{
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q){
if(root == nullptr) return nullptr;
if(p->val > q->val) return lowestCommonAncestor(root, q, p);// 保证p.val < q.val,便于讨论
if(root->val >= p->val && root->val <= q->val) return root;
if(root->val > q->val) return lowestCommonAncestor(root->left, p, q);// p和q都在左子树
else return lowestCommonAncestor(root->right, p, q);//其余条件都排除了,只剩root.val < p.val这一个,所以这个else是安全的
}
};
7.二叉树的直径
所谓二叉树的直径,就是左右子树的最大深度之和。
那么直接想法是对每个节点计算左右子树的最大高度,得出每个节点的直径,从而得出最大的那个直径。
class Solution {
public:
int maxDiameter = 0; // 存储最大直径
int diameterOfBinaryTree(TreeNode* root) {
maxdepth(root); // 调用辅助函数
return maxDiameter; // 返回已计算的最大直径
}
int maxdepth(TreeNode* root) {
if (root == nullptr) return 0; // 空节点的深度为0
int leftMax = maxdepth(root->left); // 左子树的最大深度
int rightMax = maxdepth(root->right); // 右子树的最大深度
int myDiameter = leftMax + rightMax; // 经过当前节点的最长路径长度
maxDiameter = max(maxDiameter, myDiameter); // 更新全局最大直径
return max(leftMax, rightMax) + 1; // 返回节点的最大深度
}
};
maxdepth
函数最后返回 max(leftMax, rightMax) + 1
是为了:
- 计算当前节点的最大深度,以便于父节点计算其最大深度。
- 更新整个二叉树的直径,即最长路径的长度。
8.二叉树中最大路径和
二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
oneSideMax 函数和上述几道题中都用到的 maxDepth 函数非常类似,只不过 maxDepth 计算最大深度,oneSideMax 计算「单边」最大路径和,只记录左子树或者右子树最大路径和,然后在后序遍历的时候顺便计算题目要求的最大路径和。
class Solution{
public:
int res = INT_MIN;
int maxPathSum(TreeNode* root){
if(root == nullptr) return 0;
oneSideMax(root);
return res;
}
int oneSideMax(TreeNode* root){
if(root == nullptr) return 0;
int leftMaxSum = max(0, oneSideMax(root->left));
int rightMaxSum = max(0, oneSideMax(root->right));
int pathMaxSum = root->val + leftMaxSum + rightMaxSum;
res = max(res, pathMaxSum);
return root->val + max(leftMaxSum, rightMaxSum);
}
9.二叉树的前序遍历
class Solution {
public:
vector<int> res;
vector<int> preorderTraversal(TreeNode* root) {
traverse(root);
return res;
}
void traverse(TreeNode* root){
if(root == nullptr) return;
res.push_back(root->val);
traverse(root->left);
traverse(root->right);
}
};
10.从前序与中序遍历序列构造二叉树
给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
构造二叉树,第一件事一定是找根节点,然想办法构造左右子树。前序遍历的第一个结果就是根节点的值,然后再根据中序遍历结果确定左右子树的节点。
class Solution {
// 存储 inorder 中值到索引的映射,找根节点在 inorder 中的位置
unordered_map<int, int> valToIndex;
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
for(int i = 0; i < inorder.size(); i ++) valToIndex[inorder[i]] = i;
return build(preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1);
}
private:
TreeNode* build(vector<int>& preorder, int preStart, int preEnd, vector<int>& inorder, int inStart, int inEnd){
if(preStart > preEnd) return nullptr;
int rootVal = preOrder[preStart];
int index = valToIndex[rootVal];
int leftSize = index - inStart;
//构造当前根节点
TreeNode* root = new TreeNode(rootVal);
//递归构造左右子树
root->left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1);
root->right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd);
return root;
11.从中序与后序遍历序列构造二叉树
给inorder和postorder数组,其中inorder是二叉树中的中序遍历,postorder是同一棵树的后序遍历。
class Solution {
public:
unordered_map<int, int> valToIndex;
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
for(int i = 0; i < inorder.size(); i ++) valToIndex[inorder[i]] = i;
return build(inorder, 0, inorder.size() - 1, postorder, 0, postorder.size() - 1);
}
TreeNode* build(vector<int>& inorder, int inPre, int inEnd, vector<int>& postorder, int postPre, int postEnd){
if(postPre > postEnd) return nullptr;
int rootVal = postorder[postEnd];
int index = valToIndex[rootVal];
int leftsize = index - inPre;
TreeNode* root = new TreeNode(rootVal);
root->left = build(inorder, inPre, index - 1, postorder, postPre, postPre + leftsize - 1);
root->right = build(inorder, index + 1, inEnd, postorder, postPre + leftsize, postEnd - 1);
return root;
}
};
12.二叉树的右视图
给定一个二叉树的根节点root,想象自己站在它的右侧,按照从顶部到底部的顺序,从右侧所能看到的节点值。
BFS层序遍历,每一层最后一个节点就是二叉树的右侧视图,可以把BFS反过来,从右往左遍历每一行,进一步提升效率。在每一层的位置记录一下第一个
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> res;
if(root == nullptr) return res;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
TreeNode* last = q.front();
int size = q.size();
for(int i = 0; i < size; i ++){
TreeNode* cur = q.front();
q.pop();
if(cur->right != nullptr) q.push(cur->right);
if(cur->left != nullptr) q.push(cur->left);
}
res.push_back(last->val);
}
return res;
}
};
13.二叉树最大宽度
给你一棵二叉树的根节点 root ,返回树的 最大宽度 。
树的 最大宽度 是所有层中最大的 宽度 。
每一层的 宽度 被定义为**该层最左和最右的非空节点(即,两个端点)之间的长度。**将这个二叉树视作与满二叉树结构相同,两端点间会出现一些延伸到这一层的 null 节点,这些 null 节点也计入长度。
题目数据保证答案将会在 32 位 带符号整数范围内。
本题的关键在于要给二叉树节点按行进行编号,然后就可以通过每一行的最左侧和最右侧节点的编号推算出这一行的宽度,进而算出最大宽度。
假设父节点的编号是 x,左子节点就是 2 * x,右子节点就是 2 * x + 1。这个特性常见于完全二叉树的题目当中。
使用一个结构体来记录下节点和对应的编号
class Solution {
struct Pair{
TreeNode* node;
unsigned long long id;//int不够,编号都转换成unsigned long long
Pair(TreeNode* node, unsigned long long id) : node(node), id(id) {}
};
public:
int widthOfBinaryTree(TreeNode* root) {
if(root == nullptr) return 0;
unsigned long long maxWidth = 0;
queue<Pair> q;
q.push(Pair(root, 1));
while(!q.empty()){
int size = q.size();
unsigned long long start = 0, end = 0;
for(int i = 0; i < size; i ++){
Pair cur = q.front();
q.pop();
TreeNode* curNode = cur.node;
unsigned long long curId = cur.id;
if(i == 0) start = curId;
if(i == size - 1) end = curId;
if(curNode->left != nullptr) q.push(Pair(curNode->left, curId * 2));
if(curNode->right != nullptr) q.push(Pair(curNode->right, curId * 2 + 1));
}
maxWidth = max(maxWidth, end - start + 1); //因为end - start + 1是unsigned long long类型,所以maxWidth也必须是这个类型才可以,或者将end - start + 1单独强制转换成int,static_cast<int>(end - start + 1)
}
return maxWidth;
}
};
14.二叉树的最大深度
class Solution {
public:
int res = 0;
int maxDepth(TreeNode* root) {
if(root == nullptr) return 0;
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
res = max(leftDepth, rightDepth) + 1;
return res;
}
};
15.N叉树的最大深度
给定一个N叉树,找到其最大深度。
class Solution {
public:
int maxDepth(Node* root) {
if(root == nullptr) return 0;
int subTreeMaxDepth = 0;
for(Node* child : root->children) subTreeMaxDepth = max(subTreeMaxDepth, maxDepth(child));
return 1 + subTreeMaxDepth;
}
};
16.二叉树的最小深度
最小深度不能像最大深度一样用return 1 + min(leftDepth, rightDepth),因为假设是一个节点连着一个叶子节点,另一边是空,那么min(leftDepth, rightDepth)将返回0,这导致当前节点的计算深度实际上只为1,这是错误的,因为实际上有两个节点。
所以这个地方应该分别计算
class Solution {
public:
int res = 0;
int minDepth(TreeNode* root) {
if(root == nullptr) return 0;
int leftDepth = minDepth(root->left);
int rightDepth = minDepth(root->right);
if(root->left == nullptr || root->right == nullptr) res = max(leftDepth, rightDepth) + 1;
if(root->left != nullptr && root->right != nullptr) res = min(leftDepth, rightDepth) + 1;
return res;
}
};
17.子树的最大平均值
会员题。
给你一棵二叉树的根节点root,找出这棵树的每一棵子树的平均值中的最大值。
- 以value == 5的节点作为子树的根节点,得到的平均值为4
- 以value == 6的节点作为子树的根节点,得到的平均值为6
- 以value == 1的节点作为子树的根节点,得到的平均值为1
定义一个函数helper(node),返回一个arr[]数组,其中arr[0]表示以node为根节点的子树的所有元素和,arr[1]表示以node为根节点的子树的所有元素的个数。
于是,平均数就是两个相除。
class Solution {
public:
double maxMean = 0.0;
double maximumAverageSubtree(TreeNode* root) {
if (root == nullptr) return 0.0;
helper(root);
return maxMean;
}
private:
pair<int, int> helper(TreeNode* root) {
if (root == nullptr) return make_pair(0, 0);
pair<int, int> left = helper(root->left);
pair<int, int> right = helper(root->right);
// Sum of values in the subtree
int sum = left.first + right.first + root->val;
// Total number of nodes in the subtree
int count = left.second + right.second + 1;
// Update the maximum average found so far
maxMean = max(maxMean, (double)sum / count);
return make_pair(sum, count);
}
};
18.求根节点到叶节点的数字之和
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:
例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。计算从根节点到叶节点生成的 所有数字之和 。
做法就是为了获取所有路径数字之和,递归遍历一遍二叉树,沿路记录下来路径上的数字,到叶子节点的时候求和。因为要求当前节点到叶子结点的序列,所以是前序遍历
class Solution {
public:
int res = 0;
int sumNumbers(TreeNode* root) {
traverse(root, 0);
return res;
}
void traverse(TreeNode* root, int sum)
{
sum = sum * 10 + root->val;
if(root->left == nullptr && root->right == nullptr)
{
res += sum;
return;
}
if(root->left)
traverse(root->left, sum);
if(root->right)
traverse(root->right, sum);
}
};
19.另一棵树的子树
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。
class Solution {
public:
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
if (root == nullptr) return false; // 如果主树为空,不可能包含子树,返回false
if (subRoot == nullptr) return true; // 如果子树为空,空树被认为是任何树的子树,返回true
// 检查当前节点的树与子树是否相同,或者子树是否存在于左子树或右子树中
return sameTree(root, subRoot) || isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}
bool sameTree(TreeNode* root, TreeNode* subRoot) {
if (root == nullptr || subRoot == nullptr) return root == subRoot; // 如果任一树为空,只有当两者都为空时返回true
// 检查当前节点的值是否相同,并且递归检查左右子树
return root->val == subRoot->val && sameTree(root->left, subRoot->left) && sameTree(root->right, subRoot->right);
}
};
20.对称二叉树
给一个二叉树的根节点,检查它是否轴对称
判断两棵树是否镜像对称,只要判断两棵子树都是镜像对称的就行了。如果用迭代的方式,可以使用 BFS 层序遍历,把每一层的节点求出来,然后用左右双指针判断每一层是否是对称的。
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if(root == nullptr) return true;
return check(root->left, root->right);
}
bool check(TreeNode* left, TreeNode* right){
if(left == nullptr || right == nullptr) return left == right;
if(left->val != right->val) return false;
return check(left->right, right->left) && check(left->left, right->right);
}
};
21.子结构判断
给定两棵二叉树 tree1 和 tree2,判断 tree2 是否以 tree1 的某个节点为根的子树具有 相同的结构和节点值 。
注意,空树 不会是以 tree1 的某个节点为根的子树具有 相同的结构和节点值 。
空树是任何树的子结构
class Solution {
public:
bool isSubStructure(TreeNode* A, TreeNode* B) {
if(A == nullptr || B == nullptr) return false;
// 对于树A中的一个节点,如果A->val == B->val, 则A可以作为更节点去尝试匹配
if(A->val == B->val && compareTree(A, B)) return true;
return isSubStructure(A->left, B) || isSubStructure(A->right, B);
}
bool compareTree(TreeNode* rootA, TreeNode* rootB){
if(rootB == nullptr) return true;
if(rootA == nullptr && rootB != nullptr) return false;
if(rootA->val != rootB->val) return false;
return compareTree(rootA->left, rootB->left) && compareTree(rootA->right, rootB->right);
}
};
22.寻找重复的子树
给你一棵二叉树的根节点root,返回所有重复的子树。
对于同一类的重复子树,你只需要返回其中任意 一棵 的根结点即可。
如果两棵树具有 相同的结构 和 相同的结点值 ,则认为二者是 重复 的。
将二叉树序列化的形式,建立哈希表,统计每次出现的次数,添加到结果集当中
class Solution {
public:
vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
vector<TreeNode*> res;
unordered_map<string, int> mp;
helper(root, res, mp);
return res;
}
string helper(TreeNode* root, vector<TreeNode*>& res, unordered_map<string, int>& mp){
string str;
if(!root) return "#";
str = to_string(root->val) + ' ' + helper(root->left,result,mp) + ' '+helper(root->right,result,mp);
23.相同的树
编写一个函数,用来检验两棵树是否相同。
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if(p == nullptr && q == nullptr) return true;
if(p == nullptr || q == nullptr) return false;
if(p->val != q->val) return false;
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
};
24.平衡二叉树
给一个二叉树,判断他是不是平衡的
左右子树高度差不超过1就行
计算一次最大深度,计算的过程中在后序位置判断二叉树是否平衡
class Solution {
private:
bool isBalancedTree = true;
int maxDepth(TreeNode* root){
if(root == nullptr) return 0;
int leftMaxDepth = maxDepth(root->left);
int rightMaxDepth = maxDepth(root->right);
if(abs(rightMaxDepth - leftMaxDepth) > 1) isBalancedTree = false;
return 1 + max(leftMaxDepth, rightMaxDepth);
}
public:
bool isBalanced(TreeNode* root) {
maxDepth(root);
return isBalancedTree;
}
};
25.二叉树展开为链表
class Solution {
public:
void flatten(TreeNode* root) {
if(root == nullptr) return;
flatten(root->left);
flatten(root->right);
//后序位置
//1.左右子树已经被拉平成一条链表
TreeNode* left = root->left;
TreeNode* right = root->right;
//2.将左子树作为右子树
root->left = nullptr;
root->right = left;
//3.将原先的右子树接到当前右子树的末端
TreeNode* p = root;
while(p->right != nullptr) p = p->right;
p->right = right;
}
};
26.将二叉搜索树转化为排序的双向链表
将一个 二叉搜索树 就地转化为一个 已排序的双向循环链表
对于双向循环列表,你可以将左右孩子指针作为双向循环链表的前驱和后继指针,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点
特别地,我们希望可以 就地 完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中最小元素的指针
中序线索化的变型
- 使用队列,将二叉搜索树通过中序遍历的方式,依次将节点放入队列中。由于二叉搜索树的特性,通过中序遍历得到的序列是递增的。因此,放入队列中的节点顺序就是转换后双向链表的顺序。
- 从队列中弹出节点,将弹出的节点与前一个节点(pre)进行连接。具体的操作是:将当前节点(cur)的左子节点指针指向前一个节点(pre),将前一个节点的右子节点指针指向当前节点。然后,更新前一个节点为当前节点。
- 当队列为空时,说明所有节点都已经处理完毕。此时,需要将头节点和尾节点进行连接,形成一个环形双向链表。
- 返回头节点。
双向链表需要定义两个变量cur和pre,关系如下
class Solution {
public:
TreeNode* treeToDoublyList(TreeNode* root) {
if (!root) return nullptr;
queue<Node*> queue;
inorderTraversal(root, queue); // Step 1: In-order traversal to fill the queue
Node* head = nullptr;
Node* pre = nullptr;
// Step 2: Re-link nodes in double linked list style
while (!queue.empty()) {
Node* cur = queue.front();
queue.pop();
if (!head) {
head = cur; // the smallest element becomes head
}
if (pre) {
pre->right = cur;
cur->left = pre;
}
pre = cur; // update pre to current
}
// Step 3: Make the list circular
pre->right = head;
head->left = pre;
return head;
}
private:
void inorderTraversal(Node* node, queue<Node*>& queue) {
if (!node) return;
inorderTraversal(node->left, queue);
queue.push(node);
inorderTraversal(node->right, queue);
}
};
27.验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
初学者做这题很容易有误区:BST 不是左小右大么,那我只要检查 root.val > root.left.val 且 root.val < root.right.val 不就行了?
因为 BST 左小右大的特性是指 root.val 要比左子树的所有节点都更大,要比右子树的所有节点都小,你只检查左右两个子节点当然是不够的。
正确解法是通过使用辅助函数,增加函数参数列表,在参数中携带额外信息,将这种约束传递给子树的所有节点,这也是二叉搜索树算法的一个小技巧吧。
class Solution {
public:
bool isValidBST(TreeNode* root) {
return checkBST(root, nullptr, nullptr);
}
bool checkBST(TreeNode* root, TreeNode* min, TreeNode* max){
if(root == nullptr) return true;
if(min != nullptr && root->val <= min->val) return false;
if(max != nullptr && root->val >= max->val) return false;
return checkBST(root->left, min, root) && checkBST(root->right, root, max);
}
};
28.二叉树的完全性检验
给你一棵二叉树的根节点root,请你判断这棵树是否是一棵完全二叉树。
条件:
- 除了最后一层,每一层都被完全填满
- 最后一层中的所有节点都尽可能靠左。
如果按照 BFS 层序遍历的方式遍历完全二叉树,队列最后留下的应该都是空指针
class Solution {
public:
bool endNull = false;
bool isCompleteTree(TreeNode* root) {
//BFS检验
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
int size = q.size();
for(int i = 0; i < size; i ++){
TreeNode* cur = q.front();
q.pop();
if(cur == nullptr) endNull = true;
else{
if(endNull) return false;
q.push(cur->left);// 在else里面,不能写在外面,cur为空的时候不用继续加入新节点
q.push(cur->right);
}
}
}
return true;
}
};
29.完成二叉树的节点个数
给你一棵完全二叉树的根节点root,求出该树的节点个数。
一棵完全二叉树,至少有一棵是满二叉树
计算满二叉树的节点个数不用一个个节点去数,可以直接通过树高算出来,这也是这道题提高效率的关键点。
class Solution {
public:
int countNodes(TreeNode* root) {
TreeNode* l = root;
TreeNode* r = root;
int heightL = 0, heightR = 0;
while(l != nullptr){
l = l->left;
heightL ++;
}
while(r != nullptr){
r = r->right;
heightR ++;
}
if(heightL == heightR) return (int)pow(2, heightL) - 1;
return countNodes(root->left) + countNodes(root->right) + 1;
}
};
30.删除二叉搜索树中的节点
给定一个二叉搜索树的根节点root和一个值key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
- A刚好是末端节点,两个子节点都为空,那么可以直接删除
- A只有一个非空子节点,那么需要让这个孩子接替自己的位置
- A有两子节点,为了不破坏BST的性质,A必须找到左子树中最大的节点或者是右子树中最小的节点来接替自己
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if(root == nullptr) return nullptr;
if(root->val == key){
// 情况12
if(root->left == nullptr) return root->right;
if(root->right == nullptr) return root->left;
//情况3
// 找到
TreeNode* minNode = getMin(root->right);
// 删除
root->right = deleteNode(root->right, minNode->val);
// 替换
minNode->left = root->left;
minNode->right = root->right;
root = minNode;
}
else if(root->val > key) root->left = deleteNode(root->left, key);
else if(root->val < key) root->right = deleteNode(root->right, key);
return root;
}
TreeNode* getMin(TreeNode* node){
while(node->left != nullptr) node = node->left;
return node;
}
};
31.寻找二叉树中的目标节点
某公司组织架构以二叉搜索树形式记录,请返回第cnt大的员工编号。
class Solution {
private:
int res = 0;
int rank = 0;
void traverse(TreeNode* root, int cnt){
if(root == nullptr) return;
traverse(root->right, cnt);
rank++;
if(cnt == rank){
res = root->val;
return;
}
traverse(root->left, cnt);
}
public:
int findTargetNode(TreeNode* root, int cnt) {
traverse(root, cnt);
return res;
}
};
32.二叉搜索树中第k小的元素
给定二叉搜索树的根节点root,和一个整数k,设计一个算法查找其中第k小的元素(从1开始计数)
BST 的中序遍历结果是有序的(升序),所以用一个外部变量记录中序遍历结果第 k 个元素即是第 k 小的元素。
class Solution {
private:
int res = 0;
int rank = 0;
void traverse(TreeNode* root, int k){
if(root == nullptr) return;
traverse(root->left, k);
//中序位置
rank ++;
if(rank == k){
res = root->val;
return;
}
traverse(root->right, k);
}
};
public:
int kthSmallest(TreeNode* root, int k) {
traverse(root, k);
return res;
}
33.删除给定值的叶子节点
给你一棵以 root 为根的二叉树和一个整数 target ,请你删除所有值为 target 的 叶子节点 。
注意,一旦删除值为 target 的叶子节点,它的父节点就可能变成叶子节点;如果新叶子节点的值恰好也是 target ,那么这个节点也应该被删除。
也就是说,你需要重复此过程直到不能继续删除。
遍历所有叶子节点,然后判断是否需要删除即可,删除叶子节点也非常简单,return null让父节点接受即可,难点在于删除操作是循环的,一直删到叶子结点不存在target为止。
这里涉及到后序遍历,一个节点在后序位置接受左右子树的返回值,才能知道自己的叶子节点是否都被删除掉了,以此来判断自己是不是变成了叶子节点
class Solution {
public:
TreeNode* removeLeafNodes(TreeNode* root, int target) {
if(root == nullptr) return nullptr;
// 如果左右子节点需要被删除,先递归删除它们
root->left = removeLeafNodes(root->left, target);
root->right = removeLeafNode(root->right, target);
// 后序遍历位置,此时节点 root 直到自己是否需要被删除
//如果当前节点是一个叶子节点并且其值等于 target,则通过返回 nullptr 来告诉调用它的父节点函数,这个节点应该被删除。
//这意味着在父节点中,指向这个被删除节点的指针(left 或 right)将被设置为 nullptr。
if(root->val == target && root->left == nullptr && root->right == nullptr)
return root;
}
};
34.把二叉搜索树转换为累加树
给出二叉搜索树的根节点,该树的节点值各不相同,请你将其转换为累加树,使每个节点node的新值等于原树种大于或等于node.val的值之和。
求树中大于等于该节点值的所有节点的总和
8,8+7=15,15+6=21,21+5=26,右中左
class Solution {
public:
int sum = 0;
TreeNode* convertBST(TreeNode* root) {
if(root == nullptr) return nullptr;
convertBST(root->right);
root->val += sum;
sum = root->val;
convertBST(root->left);
return root;
}
};
35.合并二叉树
给你两棵二叉树root1和root2。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。
合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
可以看做是在遍历root1这一棵二叉树,顺便把root2的节点合并过来。
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(root1 == nullptr) return root2;
if(root2 == nullptr) return root1;
root1->val += root2->val;
root1->left = mergeTrees(root1->left, root2->left);
root1->right = mergeTrees(root1->right, root2->right);
return root1;
}
};
36.翻转二叉树
给你一棵二叉树的根节点,翻转这棵二叉树,并返回其根节点。
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root == nullptr){
return nullptr;
}
invertTree(root->left);
invertTree(root->right);
TreeNode* tmp = root->right;
root->right = root->left;
root->left = tmp;
return root;
}
};
37.二叉树中所有距离为k的节点
给定一个二叉树,具有根节点root,一个目标节点target,和一个整数值k,返回到目标节点target距离为k的所有节点的值的数组。
其中树上所有节点的值val是不同的
parent来记录当前节点
class Solution {
private:
void traverse(TreeNode* root, TreeNode* parentNode){
if(root == nullptr) return;
parent[root->val] = parentNode;
traverse(root->left, root);
traverse(root->right, root);
}
public:
unordered_map<int, TreeNode*> parent;
vector<int> distanceK(TreeNode* root, TreeNode* target, int k) {
traverse(root, nullptr);
queue<TreeNode*> q;
q.push(target);
unordered_set<int> visited;
visited.insert(target->val);
int dist = 0;
vector<int> res;
while(!q.empty()){
int size = q.size();
for(int i = 0; i < size; i ++){
TreeNode* cur = q.front();
q.pop();
if(dist == k){
res.push_back(cur->val);
}
//向父节点,左孩子节点,右孩子节点三个方向扩散
TreeNode* parentNode = parent[cur->val];
if(parentNode != nullptr && visited.find(parentNode->val) == visited.end()){
visited.insert(parentNode->val);
q.push(parentNode);
}
if(cur->left != nullptr && visited.find(cur->left->val) == visited.end()){
visited.insert(cur->left->val);
q.push(cur->left);
}
if(cur->right != nullptr && visited.find(cur->right->val) == visited.end()){
visited.insert(cur->right->val);
q.push(cur->right);
}
}
dist ++;
}
return res;
}
};
38.路径总和II
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
遍历的思维很简单,只要遍历一遍二叉树,就可以把所有符合条件的路径找出来。为了维护经过的路径,在进入递归的时候要在 path 列表添加节点,结束递归的时候删除节点。
class Solution {
private:
vector<vector<int>> res;
void traverse(TreeNode* root, vector<int> path, int targetSum){
if(root == nullptr) return;
int remain = targetSum - root->val;
//找到了一条路径
if(root->left == nullptr && root->right == nullptr){
if(remain == 0){
path.push_back(root->val);
res.push_back(path);
path.pop_back();
}
return;//继续找
}
//维护路径列表
path.push_back(root->val);
traverse(root->left, path, remain);
path.pop_back();
path.push_back(root->val);
traverse(root->right, path, remain);
path.pop_back();
}
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
if(root == nullptr) return res;
traverse(root, vector<int>(), targetSum);
return res;
}
};
39.寻找重复的子树
给你一棵二叉树的根节点 root ,返回所有 重复的子树 。
对于同一类的重复子树,你只需要返回其中任意 一棵 的根结点即可。
如果两棵树具有 相同的结构 和 相同的结点值 ,则认为二者是 重复 的。
如果想知道以自己为根的子树是不是重复的,是否应该被加入结果列表中,需要知道什么信息?上面的的[2, 4]其实是返回了以2为根节点的TreeNode型
需要知道以下两点:
1、以我为根的这棵二叉树(子树)长啥样?-> 需要在后序的位置进行操作
2、以其他节点为根的子树都长啥样?-> 前序后序层序都可以反映二叉树的结构(中序不行),所以可以将二叉树序列化
class Solution {
private:
vector<TreeNode*> res;
unordered_map<string, int> subTree;
string serialize(TreeNode* root){
if(root == nullptr) return "#";
string left = serialize(root->left);
string right = serialize(root->right);
string myself = left + ',' + right + ',' + to_string(root->val);
int freq = subTree[myself];
if(freq == 1){
res.push_back(root);
}
subTree[myself]++;
return myself;//随便返回的,需要一个返回值
}
public:
vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
serialize(root);
return res;
}
};
40.二叉树的序列化和反序列化
序列化和39是一个意思,反序列化就是给一串string字符串,将其还原成树。
什么样的序列化数据可以反序列化出唯一的一颗二叉树?只要给了空指针信息,前后序层序就可以缺点唯一的二叉树
代码以前序为例
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if (!root) return "#";
return to_string(root->val) + "," + serialize(root->left) + "," + serialize(root->right);
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
vector<string> nodes;
string cur = "";
//过滤掉逗号
for (char c : data) {
if (c == ',') {
nodes.push_back(cur);
cur = "";
} else {
cur += c;
}
}
if (!cur.empty()) nodes.push_back(cur); // Add the last node
int index = 0;
return deserialize(nodes, index);
}
TreeNode* deserialize(vector<string>& nodes, int& index) {
if (index >= nodes.size() || nodes[index] == "#") {
index++;
return nullptr;
}
TreeNode* root = new TreeNode(stoi(nodes[index++]));
root->left = deserialize(nodes, index);
root->right = deserialize(nodes, index);
return root;
}
};