二叉树
二叉树的深度
二叉树的深度 - 牛客
问题描述
一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
C++
class Solution {
public:
int TreeDepth(TreeNode* root) {
if (root == NULL) return 0;
return max(TreeDepth(root->left), TreeDepth(root->right)) + 1;
}
};
二叉树的宽度
思路
- 层序遍历(队列)
C++
class Solution {
public:
int widthOfBinaryTree(TreeNode* root) {
if (root == nullptr)
return 0;
queue<TreeNode*> Q;
Q.push(root);
int ans = 1;
while(!Q.empty()) {
int cur_w = Q.size(); // 当前层的宽度
ans = max(ans, cur_w);
for (int i=0; i<cur_w; i++) {
auto p = Q.front();
Q.pop();
if (p->left)
Q.push(p->left);
if (p->right)
Q.push(p->right);
}
}
return ans;
}
};
二叉树最大宽度(LeetCode)
LeetCode - 662. 二叉树最大宽度
问题描述
给定一个二叉树,编写一个函数来获取这个树的最大宽度。
树的宽度是所有层中的最大宽度。
这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。
每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。
示例 1:
输入:
1
/ \
3 2
/ \ \
5 3 9
输出: 4
解释: 最大值出现在树的第 3 层,宽度为 4 (5,3,null,9)。
示例 2:
输入:
1
/
3
/ \
5 3
输出: 2
解释: 最大值出现在树的第 3 层,宽度为 2 (5,3)。
思路
- 本题在二叉树宽度的基础上加入了满二叉树的性质,即每层都有 2 n − 1 2^{n-1} 2n−1 个节点。某节点的左孩子的标号是 2 n 2n 2n, 右节点的标号是 2 n + 1 2n + 1 2n+1。
- 注:如果在循环中会增删容器中的元素,则不应该在
for
循环中使用size()
方法,该方法的返回值会根据容器的内容动态改变。
C++
class Solution {
public:
int widthOfBinaryTree(TreeNode* root) {
if (root == nullptr)
return 0;
deque<pair<TreeNode*, int>> Q; // 记录节点及其在满二叉树中的位置
Q.push_back({ root, 1 });
int ans = 0;
while (!Q.empty()) {
int cur_n = Q.size();
int cur_w = Q.back().second - Q.front().second + 1; // 当前层的宽度
ans = max(ans, cur_w);
//for (int i = 0; i<Q.size(); i++) { // err: Q.size() 会动态改变
for (int i = 0; i<cur_n; i++) {
auto p = Q.front();
Q.pop_front();
if (p.first->left != nullptr)
Q.push_back({ p.first->left, p.second * 2 });
if (p.first->right != nullptr)
Q.push_back({ p.first->right, p.second * 2 + 1 });
}
}
return ans;
}
};
二叉树中的最长路径
思路
- 基于二叉树的深度
- 对任一子树而言,则经过该节点的一条最长路径为其
左子树的深度 + 右子树的深度 + 1
- 遍历树中每个节点的最长路径,其中最大的即为整个树的最长路径
为什么最长路径不一定是经过根节点的那条路径?
判断平衡二叉树 TODO
判断树 B 是否为树 A 的子结构
树的子结构 - 牛客
题目描述
输入两棵二叉树A,B,判断B是不是A的子结构。
约定空树不是任意一个树的子结构。
- 图示
思路
- 递归
- 有两个递归的点:一、递归寻找树 A 中与树 B 根节点相同的子节点;二、递归判断子结构是否相同
Code(递归)
class Solution {
public:
bool HasSubtree(TreeNode* p1, TreeNode* p2) {
if (p1 == nullptr || p2 == nullptr) // 约定空树不是任意一个树的子结构
return false;
return isSubTree(p1, p2) // 判断子结构是否相同
|| HasSubtree(p1->left, p2) // 递归寻找树 A 中与树 B 根节点相同的子节点
|| HasSubtree(p1->right, p2);
}
bool isSubTree(TreeNode* p1, TreeNode* p2) {
if (p2 == nullptr) return true; // 注意这两个判断的顺序
if (p1 == nullptr) return false;
if (p1->val == p2->val)
return isSubTree(p1->left, p2->left) // 递归判断左右子树
&& isSubTree(p1->right, p2->right);
else
return false;
}
};
利用前序和中序重建二叉树
重建二叉树 - 牛客
题目描述
根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
思路
- 前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为左子树的中序遍历结果,右部分为右子树的中序遍历的结果。
- 根据左右子树的长度,可以从前序遍历的结果中划分出左右子树的前序遍历结果
- 接下来就是递归过程
- 注意:必须序列中的值不重复才可以这么做
- 示例
前序 1,2,4,7,3,5,6,8 中序 4,7,2,1,5,3,8,6 第一层 根节点 1 根据根节点的值(不重复),划分中序: {4,7,2} 和 {5,3,8,6} 根据左右子树的长度,划分前序: {2,4,7} 和 {3,5,6,8} 从而得到左右子树的前序和中序 左子树的前序和中序:{2,4,7}、{4,7,2} 右子树的前序和中序:{3,5,6,8}、{5,3,8,6} 第二层 左子树的根节点 2 右子树的根节点 3 ...
Code(Python)
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# 返回构造的TreeNode根节点
def reConstructBinaryTree(self, pre, tin):
if len(pre) < 1:
return None
root = TreeNode(pre[0])
index = tin.index(root.val) # 注意值不重复,才可以这么做
root.left = self.reConstructBinaryTree(pre[1: 1+index], tin[:index])
root.right = self.reConstructBinaryTree(pre[1+index:], tin[index+1:])
return root
二叉树的序列化与反序列化
序列化二叉树 - NowCoder
题目描述
请实现两个函数,分别用来序列化和反序列化二叉树。
接口如下:
char* Serialize(TreeNode *root);
TreeNode* Deserialize(char *str);
空节点用 '#' 表示,节点之间用空格分开
- 比如中序遍历就是一个二叉树序列化
- 反序列化要求能够通过序列化的结果还原二叉树
思路
- 前序遍历
Code
class Solution {
stringstream ss_fw;
stringstream ss_bw;
public:
char* Serialize(TreeNode *root) {
dfs_fw(root);
char ret[1024];
return strcpy(ret, ss_fw.str().c_str());
// return (char*)ss.str().c_str(); // 会出问题,原因未知
}
void dfs_fw(TreeNode *node) {
if (node == nullptr) {
ss_fw << "#";
return;
}
ss_fw << node->val;
ss_fw << " ";
dfs_fw(node->left);
ss_fw << " ";
dfs_fw(node->right);
}
TreeNode* Deserialize(char *str) {
if (strlen(str) < 1) return nullptr;
ss_bw << str;
return dfs_bw();
}
TreeNode* dfs_bw() {
if (ss_bw.eof())
return nullptr;
string val; // 因为 "#",用 int 或 char 接收都会有问题
ss_bw >> val;
if (val == "#")
return nullptr;
TreeNode* node = new TreeNode{ stoi(val) };
node->left = dfs_bw();
node->right = dfs_bw();
return node;
}
};
最近公共祖先
《剑指 Offer》 7.2 案例二
问题描述
给定一棵树的根节点 root,和其中的两个节点 p1 和 p2,求它们的最小公共父节点。
如果树是二叉搜索树
- 找到第一个满足
p1 < root < p2
的根节点,即为它们的最小公共父节点; - 如果寻找的过程中,没有这样的
root
,那么p1
和p2
的最小公共父节点必是它们之一,此时遍历到p1
或p2
就返回。
如果树的节点中保存有指向父节点的指针
- 问题等价于求两个链表的第一个公共节点
如果只是普通的二叉树
236. 二叉树的最近公共祖先 - LeetCode
- 利用两个辅助链表/数组,保存分别到
p1
和p2
的路径; - 则
p1
和p2
的最小公共父节点就是这两个链表的最后一个公共节点 - C++
class Solution { bool getPath(TreeNode* root, TreeNode* p, deque<TreeNode*>& path) { if (root == nullptr) return false; path.push_back(root); if (p == root) return true; bool found = false; if (!found) found = getPath(root->left, p, path); if (!found) found = getPath(root->right, p, path); if (!found) path.pop_back(); return found; } public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { deque<TreeNode*> path_p; auto found_p = getPath(root, p, path_p); deque<TreeNode*> path_q; auto found_q = getPath(root, q, path_q); TreeNode* ret = root; if (found_p && found_q) { auto it_p = path_p.begin(); auto it_q = path_q.begin(); while (it_p != path_p.end() && it_q != path_q.end()) { if (*it_p != *it_q) return ret; ret = *it_p; it_p++, it_q++; } return ret; } return nullptr; } };
获取节点的路径
二叉树
#include <deque>
bool getPath(TreeNode* root, TreeNode* p, deque<TreeNode*>& path) {
if (root == nullptr)
return false;
path.push_back(root);
if (p == root)
return true;
bool found = false;
if (!found)
found = getPath(root->left, p, path);
if (!found)
found = getPath(root->right, p, path);
if (!found)
path.pop_back();
return found;
}
非二叉树
#include <deque>
struct TreeNode {
int val;
std::vector<TreeNode*> children;
};
bool getPath(const TreeNode* root, const TreeNode* p, deque<const TreeNode*>& path) {
if (root == nullptr)
return false;
path.push_back(root);
if (root == p)
return true;
bool found = false;
auto i = root->children.begin(); // 顺序遍历每个子节点
while (!found && i < root->children.end()) {
found = GetNodePath(*i, p, path);
++i;
}
if (!found) // 如果没有找到就,说明当前节点不在路径内,弹出
path.pop_back();
return found;
}