二叉树21:阶段总结:怎么用递归解决二叉树问题

我们前面介绍了十几个算法题,里面有很多共性问题,我们可以统一梳理一下,我现在能想到,并且找到一些参考借鉴的有两个,对称性递归和路径相关问题。其他我们后面继续补充。参考地址:

https://leetcode-cn.com/problems/merge-two-binary-trees/solution/yi-pian-wen-zhang-dai-ni-chi-tou-dui-che-asg8/

不过呢,原文代码是用C++写的,我们这里转成java的。

该作者还有一篇专门分析二叉树路径相关的文章:

https://leetcode-cn.com/problems/binary-tree-paths/solution/yi-pian-wen-zhang-jie-jue-suo-you-er-cha-5f58/

1.什么是对称性递归


力扣上很多树的题目都是可以用递归很快地解决的,而这一系列递归解法中蕴含了一种很强大的递归思维:对称性递归(symmetric recursion)
什么是对称性递归?就是对一个对称的数据结构(这里指二叉树)从整体的对称性思考,把大问题分解成子问题进行递归,即不是单独考虑一部分(比如树的左子树),而是同时考虑对称的两部分(左右子树),从而写出对称性的递归代码

题型分类:
可以用对称性递归解决的二叉树问题大多是判断性问题(bool类型函数),这一类问题又可以分为以下两类:
1、不需要构造辅助函数。这一类题目有两种情况:第一种是单树问题,且不需要用到子树的某一部分(比如根节点左子树的右子树),只要利用根节点左右子树的对称性即可进行递归。第二种是双树问题,即本身题目要求比较两棵树,那么不需要构造新函数。该类型题目如下:

100. 相同的树
226. 翻转二叉树
104. 二叉树的最大深度
110. 平衡二叉树
543. 二叉树的直径
617. 合并二叉树
572. 另一个树的子树
965. 单值二叉树

2、需要构造辅助函数。这类题目通常只用根节点子树对称性无法完全解决问题,必须要用到子树的某一部分进行递归,即要调用辅助函数比较两个部分子树。形式上主函数参数列表只有一个根节点,辅助函数参数列表有两个节点。该类型题目如下:
101. 对称二叉树
剑指 Offer 26. 树的子结构

 2.解题模板

下面给出二叉树对称性递归的解题模板
1、递归结束条件:特殊情况的判断
如果是单树问题,一般来说只要进行以下判断:

if(root==null) return true/false;
if(root.left==null) return true/false/递归函数;
if(!root.right==null) return true/false/递归函数;

如果是双树问题(根节点分别为p,q),一般来说进行以下判断:

if(p==null && q===null)return true/false;
if(p==null || q==null)return true/false;

当然也不完全是这些情况,比如有的需要加上节点值的判断,需要具体问题需要具体分析

2、返回值
通常对称性递归的返回值是多个条件的复合判断语句
可能是以下几种条件判断的组合:
节点非空的判断
节点值比较判断
(单树)调用根节点左右子树的递归函数进行递归判断
(双树)调用两棵树的左右子树的递归函数进行判断

3.题目案例


空谈比较抽象,下面我们就对具体题目进行分析以及代码呈现


1.leetcode 100. 相同的树


相同的树:比较两棵树是否相同
特殊判断:如果两棵树都是空树那么必然相同;如果两棵树其中只有一棵树为空树那么必不相同
返回值:两棵树都非空+根节点值相同+左子树相同+右子树相同

bool isSameTree(TreeNode p, TreeNode q)
{
    if (p==null &&q==null)
        return true;
    return p && q && p.val == q.val && (isSameTree(p.left, q.left)) && (isSameTree(p.right, q.right));
}

2. leetcode 104. 二叉树的最大深度


求二叉树最大深度
特殊判断:空树的最大深度为0
返回值:树非空,那么最大深度就是左子树最大深度和右子树最大深度的较大者加上根节点的1
代码如下:

int height(TreeNode root)
{
    if (root==null)
        return 0;
    else
        return max(height(root.left), height(root.right)) + 1;
}

 

 3.leetcode110. 平衡二叉树


判断一棵树是不是平衡二叉树
平衡二叉树定义:左右子树最大高度差<=1
特殊判断:空树是平衡树
返回值:根节点的左右子树高度差<=1 + 左子树是平衡二叉树 +右子树是平衡二叉树
代码如下:(height函数即上一题的代码) 

bool isBalanced(TreeNode root)
{
    if (root==null)
        return true;
    return abs((height(root.left) - height(root.right)) <= 1) && isBalanced(root.left) && isBalanced(root.right);
}

 

4.leetcode965. 单值二叉树


单值二叉树:所有节点值均相等
特殊判断:1、空树是单值二叉树 2、如果左子树非空且根节点的值异与左子节点值(即根节点与左子节点不同),返回false,右子树同理
返回值:左子树是单值二叉树+右子树是单值二叉树
代码如下:

bool isUnivalTree(TreeNode root)
{
    if (root==null) 
        return true;
    if ((root.left && root.left.val != root.val) || (root.right && root.right.val != root.val))
        return false; 
    return isUnivalTree(root.left) && isUnivalTree(root.right);
}

5.leetcode 572. 另一个树的子树


判断一个数是不是另一颗树的子树
特殊判断:有一颗树为空就不成立
这道题的思路比较特殊,先判断两棵树是否是相同,如果相同那么就是满足题意的,
然后判断一棵树的左子树是否是另一颗树的子树/一棵树的右子树是否是另一颗树的子树

bool isSubtree(TreeNode root1, TreeNode root2)
{
    if (root1==null || root2==null)
        return false;
    if (isSameTree(root1, root2))
        return true;
    return isSubtree(root1.left, root2) || isSubtree(root1.right, root2);
}

6.LeetCode226. 翻转二叉树

将一棵二叉树镜像翻转
特殊判断:空树的镜像翻转树仍然是本身
思路:翻转左子树后替换右子树,翻转右子树后替换左子树

TreeNode invertTree(TreeNode root)
{
    if (root==null)
        return nullptr;
    TreeNode left = invertTree(root.left);
    TreeNode right = invertTree(root.right);
    root.left = right;
    root.right = left;
    return root;
}

7.leetcode 617. 合并二叉树


合并二叉树:将两个二叉树合并
思路:1、都是空树返回nullptr 2、其中有一个空返回另一个树的根节点
3、都不空的话先把两棵树根节点值相加,然后递归合并左右子树(以第一棵树为合并后的树)
代码如下:

TreeNode*mergeTrees(TreeNode root1, TreeNode root2)
{
    if (root1==null)
        return root2;
    if (root2==null)
        return root1;
    if (root!=null && root2!=null)
        root1.val += root2.val;
    root1.left = mergeTrees(root1.left, root2.left);    //递归合并左子树
    root1.right = mergeTrees(root1.right, root2.right); //递归合并右子树
    return root1;
}

 

8.剑指 Offer 28. 对称的二叉树


判断一棵树是否为对称二叉树
思路:构造一个辅助函数判断两棵树是否是镜像对称的,然后题目只要判断两棵这个树是否镜像对称
而比较两棵树是否镜像对称,即一棵树的左子树和另一棵树的右子树,以及一棵树的右子树和另一棵树的左子树是否镜像对称
特殊判断:都是空树满足条件;其中有一棵空树不满足条件
代码如下: 

bool isSymmetric(TreeNode root)
{
    return isMirror(root, root);
}

bool isMirror(TreeNode p, TreeNode q)
{
    if (p==null &&q==null)
        return true;
    if (p==null || q==null)
        return false;
    return (p.val == q.val) && (isMirror(p.left, q.right)) && (isMirror(p.right, q.left));
}

9.剑指 Offer 26. 树的子结构

判断一棵树是否是另一棵树的子结构,注意子结构与子树的区别
子结构不能只利用根节点进行对称性递归,需要构造辅助函数,判断当两棵树根节点值相同时一棵树是否为另一棵树子结构

// hasSubStructure函数是判断如果A,B根节点相同,B是不是A的子结构
bool hasSubStructure(TreeNode A, TreeNode B)
{
    if (B==null)        //递归结束条件1:A的一个节点B的对应位置没有,可以认为是子结构
        return true;
    if (A==null || A.val != B.val)  //递归结束条件2:B的一个节点A的对应位置没有 / A,B对应位置节点值不同,此时必然不可能是子结构
        return false;
    return hasSubStructure(A.left, B.left) && hasSubStructure(A.right, B.right); //返回值:继续在对应位置递归判断
}


bool isSubStructure1(TreeNode<T> A, TreeNode<T> B)
{
    if (A==null || B==null)   //特殊判断
        return false;
    // 根节点相同的话直接进入比较,根节点不相同看B是不是A的左/右子树的子结构
    return hasSubStructure(A, B) || isSubStructure1(A.left, B) || isSubStructure1(A.right, B);
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
中序遍历二叉树的非递归算法可以借助栈来实现。具体步骤如下: 1. 创建一个空栈。 2. 将根节点入栈。 3. 当栈非空时,重复以下步骤: - 将栈顶节点出栈,输出它的值。 - 如果该节点有右子树,则将右子树入栈。 - 如果该节点没有右子树且有左子树,则将左子树入栈。 4. 当栈为空时,遍历结束。 下面是该算法的 C 语言实现: ```c #include <stdio.h> #include <stdlib.h> // 定义二叉树节点结构体 typedef struct Node { int data; struct Node *left; struct Node *right; } Node, *Tree; // 定义栈结构体 typedef struct Stack { Node *data[100]; // 栈的最大容量为 100 int top; // 栈顶指针 } Stack; // 初始化栈 void init(Stack *s) { s->top = -1; } // 判断栈是否为空 int is_empty(Stack *s) { return s->top == -1; } // 判断栈是否已满 int is_full(Stack *s) { return s->top == 99; } // 入栈 void push(Stack *s, Node *node) { if (is_full(s)) { printf("Error: Stack is full!\n"); return; } s->top++; s->data[s->top] = node; } // 出栈 Node* pop(Stack *s) { if (is_empty(s)) { printf("Error: Stack is empty!\n"); return NULL; } Node *node = s->data[s->top]; s->top--; return node; } // 中序遍历二叉树的非递归算法 void inorder_traversal(Tree tree) { if (tree == NULL) { return; } Stack s; init(&s); Node *p = tree; while (p || !is_empty(&s)) { while (p) { push(&s, p); p = p->left; } if (!is_empty(&s)) { p = pop(&s); printf("%d ", p->data); p = p->right; } } } // 创建二叉树 Tree create_tree() { Tree tree; int data; scanf("%d", &data); if (data == -1) { // 输入 -1 表示该节点为空 tree = NULL; } else { tree = (Tree)malloc(sizeof(Node)); tree->data = data; tree->left = create_tree(); tree->right = create_tree(); } return tree; } int main() { Tree tree = create_tree(); printf("中序遍历二叉树的结果为:\n"); inorder_traversal(tree); printf("\n"); return 0; } ``` 注意,这里的二叉树节点数据类型是 `Node`,而不是 `int`,因为每个节点包含了左右子树的指针。同时,这里的栈使用了数组实现,最大容量为 100,如果需要更大的容量可以根据实际情况进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值