二叉树是每个节点最多有两个子节点的树结构,在算法领域有着广泛应用。理解二叉树的核心在于掌握其递归性质——每个子树本身也是一棵二叉树。这种自相似的特性使得递归成为处理二叉树问题的天然工具。
在解决二叉树问题时,我们需要明确三个关键点:
这种"分而治之"的思想是解决树形结构问题的通用范式。下面我们通过7个经典题目,深入剖析二叉树的各种操作技巧。
翻转二叉树的核心操作是交换每个节点的左右子树。这个看似简单的操作在实际应用中很有价值,比如在图形界面中需要镜像显示树状结构时。
cpp复制TreeNode* invertTree(TreeNode* root) {
if(root){
swap(root->left,root->right); // 当前节点处理
invertTree(root->left); // 递归处理左子树
invertTree(root->right); // 递归处理右子树
}
return root;
}
注意:交换操作必须在递归调用之前进行,这样才能保证整棵树被正确翻转。如果先递归再交换,会导致子树被多次翻转。
时间复杂度:O(n),每个节点访问一次
空间复杂度:O(h),h为树高,递归栈空间
非递归解法可以使用层序遍历(BFS):
cpp复制TreeNode* invertTree(TreeNode* root) {
if(!root) return nullptr;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
TreeNode* node = q.front();
q.pop();
swap(node->left, node->right);
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
}
return root;
}
判断二叉树是否对称,本质上是判断左右子树是否镜像对称。这需要同时遍历两棵子树进行比较。
cpp复制bool com(TreeNode* left, TreeNode* right) {
if (!left && right) return false; // 一边为空一边非空
else if (left && !right) return false;
else if (!left && !right) return true; // 两边都为空
else if(left->val != right->val) return false; // 值不相等
else return com(left->left, right->right) && // 外层比较
com(left->right, right->left); // 内层比较
}
bool isSymmetric(TreeNode* root) {
if(!root) return true;
return com(root->left, root->right);
}
使用队列实现迭代解法:
cpp复制bool isSymmetric(TreeNode* root) {
if(!root) return true;
queue<TreeNode*> q;
q.push(root->left);
q.push(root->right);
while(!q.empty()){
TreeNode* l = q.front(); q.pop();
TreeNode* r = q.front(); q.pop();
if(!l && !r) continue;
if(!l || !r || l->val != r->val) return false;
q.push(l->left); q.push(r->right);
q.push(l->right); q.push(r->left);
}
return true;
}
判断两棵树是否相同,需要同时遍历两棵树并比较每个节点的值和结构。
cpp复制bool isSameTree(TreeNode* p, TreeNode* q) {
if(!p && !q) return true; // 都为空
else if(!p || !q) return false; // 一个空一个非空
if(p->val != q->val) return false; // 值不等
return isSameTree(p->left, q->left) && // 递归比较左子树
isSameTree(p->right, q->right); // 递归比较右子树
}
树相等判断常用于:
判断subRoot是否是root的子树,可以分解为:
cpp复制bool isSameTree(TreeNode* p, TreeNode* q) {
if (!p || !q) return p == q;
return p->val == q->val &&
isSameTree(p->left, q->left) &&
isSameTree(p->right, q->right);
}
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
if (!root) return false;
return isSameTree(root, subRoot) || // 当前树匹配
isSubtree(root->left, subRoot) || // 左子树包含
isSubtree(root->right, subRoot); // 右子树包含
}
朴素解法时间复杂度为O(mn),可以通过序列化+KMP算法优化到O(m+n):
最大深度定义为从根节点到最远叶子节点的最长路径上的节点数。
cpp复制int maxDepth(TreeNode* root) {
if(!root) return 0;
int left = maxDepth(root->left); // 左子树深度
int right = maxDepth(root->right); // 右子树深度
return max(left, right) + 1; // 当前节点深度
}
使用层序遍历计算深度:
cpp复制int maxDepth(TreeNode* root) {
if(!root) return 0;
queue<TreeNode*> q;
q.push(root);
int depth = 0;
while(!q.empty()){
int size = q.size();
depth++;
while(size--){
TreeNode* node = q.front(); q.pop();
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
}
}
return depth;
}
N叉树的节点可以有多个子节点,存储在vector中。计算最大深度的思路类似,但需要遍历所有子节点。
cpp复制int maxDepth(Node* root) {
if(!root) return 0;
int deep = 0;
vector<Node*> c = root->children;
for(auto child : c){
int childdeep = maxDepth(child);
deep = max(deep, childdeep);
}
return deep + 1;
}
使用BFS计算N叉树深度:
cpp复制int maxDepth(Node* root) {
if(!root) return 0;
queue<Node*> q;
q.push(root);
int depth = 0;
while(!q.empty()){
int size = q.size();
depth++;
while(size--){
Node* node = q.front(); q.pop();
for(auto child : node->children){
if(child) q.push(child);
}
}
}
return depth;
}
最小深度是从根节点到最近叶子节点的最短路径上的节点数。注意与最大深度的区别:当某子树为空时,不能直接取min。
cpp复制int minDepth(TreeNode* root) {
if(!root) return 0;
if(!root->left && !root->right) return 1; // 叶子节点
int deep = INT_MAX;
if(root->left) deep = min(minDepth(root->left), deep);
if(root->right) deep = min(minDepth(root->right), deep);
return deep + 1;
}
错误写法:
cpp复制int minDepth(TreeNode* root) {
if(!root) return 0;
return min(minDepth(root->left), minDepth(root->right)) + 1;
}
这种写法会错误地返回1当某子树为空时,因为空子树的深度为0。
修正后的迭代解法:
cpp复制int minDepth(TreeNode* root) {
if(!root) return 0;
queue<TreeNode*> q;
q.push(root);
int depth = 1;
while(!q.empty()){
int size = q.size();
while(size--){
TreeNode* node = q.front(); q.pop();
if(!node->left && !node->right) return depth;
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
}
depth++;
}
return depth;
}
对于二叉树问题,建议从简单递归解法入手,再考虑优化和迭代解法。理解递归的调用过程对调试二叉树问题至关重要,可以画递归树帮助理解。