树刷题codetop!!暴打面试题!!!!

2024-08-23 05:36

本文主要是介绍树刷题codetop!!暴打面试题!!!!,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题源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,它会返回一个节点:

  1. 如果p和q都在以root为根的树中,函数返回的是p和q的最近公共祖先节点
  2. 如果p和q都不在以root为根的树中,函数返回null
  3. 如果p和q只有一个存在于root为根的树中,函数会返回该节点
class Solution{
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q){// base caseif(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;// 情况1if(left == nullptr && right == nullptr) return nullptr;// 情况2return  left == nullptr ? right : left;	}
};

6.二叉搜索树的最近公共祖先

  1. 如果p和q都比当前节点小,那么p和q显然都在左子树
  2. 如果p和q都比当前节点大,那么p和q显然都在右子树
  3. 一旦发现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;  // 空节点的深度为0int 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 是为了:

  1. 计算当前节点的最大深度,以便于父节点计算其最大深度。
  2. 更新整个二叉树的直径,即最长路径的长度。

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.从前序与中序遍历序列构造二叉树

给定两个整数数组 preorderinorder ,其中 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 longPair(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 subtreeint sum = left.first + right.first + root->val;// Total number of nodes in the subtreeint count = left.second + right.second + 1;// Update the maximum average found so farmaxMean = 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;  // 如果主树为空,不可能包含子树,返回falseif (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.将二叉搜索树转化为排序的双向链表

将一个 二叉搜索树 就地转化为一个 已排序的双向循环链表
对于双向循环列表,你可以将左右孩子指针作为双向循环链表的前驱和后继指针,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点
特别地,我们希望可以 就地 完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中最小元素的指针
在这里插入图片描述
中序线索化的变型

  1. 使用队列,将二叉搜索树通过中序遍历的方式,依次将节点放入队列中。由于二叉搜索树的特性,通过中序遍历得到的序列是递增的。因此,放入队列中的节点顺序就是转换后双向链表的顺序。
  2. 从队列中弹出节点,将弹出的节点与前一个节点(pre)进行连接。具体的操作是:将当前节点(cur)的左子节点指针指向前一个节点(pre),将前一个节点的右子节点指针指向当前节点。然后,更新前一个节点为当前节点。
  3. 当队列为空时,说明所有节点都已经处理完毕。此时,需要将头节点和尾节点进行连接,形成一个环形双向链表。
  4. 返回头节点。
    双向链表需要定义两个变量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 queueNode* head = nullptr;Node* pre = nullptr;// Step 2: Re-link nodes in double linked list stylewhile (!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 circularpre->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 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

  1. A刚好是末端节点,两个子节点都为空,那么可以直接删除
  2. A只有一个非空子节点,那么需要让这个孩子接替自己的位置
  3. A有两子节点,为了不破坏BST的性质,A必须找到左子树中最大的节点或者是右子树中最小的节点来接替自己
class Solution {
public:TreeNode* deleteNode(TreeNode* root, int key) {if(root == nullptr) return nullptr;if(root->val == key){// 情况12if(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 nodeint 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;}
};

这篇关于树刷题codetop!!暴打面试题!!!!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1098497

相关文章

荣耀嵌入式面试题及参考答案

在项目中是否有使用过实时操作系统? 在我参与的项目中,有使用过实时操作系统。实时操作系统(RTOS)在对时间要求严格的应用场景中具有重要作用。我曾参与的一个工业自动化控制项目就采用了实时操作系统。在这个项目中,需要对多个传感器的数据进行实时采集和处理,并根据采集到的数据及时控制执行机构的动作。实时操作系统能够提供确定性的响应时间,确保关键任务在规定的时间内完成。 使用实时操作系统的

一些其他面试题

阿里二面:那你来说说定时任务?单机、分布式、调度框架下的定时任务实现是怎么完成的?懵了。。_哔哩哔哩_bilibili 1.定时算法 累加,第二层每一个格子是第一层的总时间400 ms= 20 * 20ms 2.MQ消息丢失 阿里二面:高并发场景下引进消息队列有什么问题?如何保证消息只被消费一次?真是捏了一把汗。。_哔哩哔哩_bilibili 发送消息失败

zookeeper相关面试题

zk的数据同步原理?zk的集群会出现脑裂的问题吗?zk的watch机制实现原理?zk是如何保证一致性的?zk的快速选举leader原理?zk的典型应用场景zk中一个客户端修改了数据之后,其他客户端能够马上获取到最新的数据吗?zk对事物的支持? 1. zk的数据同步原理? zk的数据同步过程中,通过以下三个参数来选择对应的数据同步方式 peerLastZxid:Learner服务器(Follo

java常用面试题-基础知识分享

什么是Java? Java是一种高级编程语言,旨在提供跨平台的解决方案。它是一种面向对象的语言,具有简单、结构化、可移植、可靠、安全等特点。 Java的主要特点是什么? Java的主要特点包括: 简单性:Java的语法相对简单,易于学习和使用。面向对象:Java是一种完全面向对象的语言,支持封装、继承和多态。跨平台性:Java的程序可以在不同的操作系统上运行,称为"Write once,

【Kubernetes】常见面试题汇总(三)

目录 9.简述 Kubernetes 的缺点或当前的不足之处? 10.简述 Kubernetes 相关基础概念? 9.简述 Kubernetes 的缺点或当前的不足之处? Kubernetes 当前存在的缺点(不足)如下: ① 安装过程和配置相对困难复杂; ② 管理服务相对繁琐; ③ 运行和编译需要很多时间; ④ 它比其他替代品更昂贵; ⑤ 对于简单的应用程序来说,可能不

【附答案】C/C++ 最常见50道面试题

文章目录 面试题 1:深入探讨变量的声明与定义的区别面试题 2:编写比较“零值”的`if`语句面试题 3:深入理解`sizeof`与`strlen`的差异面试题 4:解析C与C++中`static`关键字的不同用途面试题 5:比较C语言的`malloc`与C++的`new`面试题 6:实现一个“标准”的`MIN`宏面试题 7:指针是否可以是`volatile`面试题 8:探讨`a`和`&a`

Laravel 面试题

PHP模块 PHP7 和 PHP5 的区别,具体多了哪些新特性? 性能提升了两倍 结合比较运算符 (<=>) 标量类型声明 返回类型声明 try…catch 增加多条件判断,更多 Error 错误可以进行异常处理 匿名类,现在支持通过new class 来实例化一个匿名类,这可以用来替代一些“用后即焚”的完整类定义 …… 了解更多查看文章底部链接 PHP7 新特性 为什么 PHP

【吊打面试官系列-Redis面试题】说说 Redis 哈希槽的概念?

大家好,我是锋哥。今天分享关于 【说说 Redis 哈希槽的概念?】面试题,希望对大家有帮助; 说说 Redis 哈希槽的概念? Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽, 集群的每个节点负责一部分 hash 槽。

【Kubernetes】常见面试题汇总(一)

目录 1.简述 etcd 及其特点? 2.简述 etcd 适应的场景? 3.简述什么是Kubernetes? 4.简述 Kubernetes和 Docker的关系? 1.简述 etcd 及其特点? (1)etcd 是Core0s 团队发起的开源项目,是一个管理配置信息和服务发现(service discovery)的项目,它的目标是构建一个高可用的分布式键值(keyvalue)数据

2018秋招C/C++面试题总结

博主从8月中旬开始大大小小面试了十几家公司,至今也许是告一段落吧,希望后面会有好结果,因此总结记录一些C/C++方向常见的问题。和大家一起学习! 参考了互联网的各种资源,自己尝试归类整理,谢谢~ 一、C和C++的区别是什么? C是面向过程的语言,C++是在C语言的基础上开发的一种面向对象编程语言,应用广泛。 C中函数不能进行重载,C++函数可以重载 C++在C的基础上增添类,C是一个结构