剑指offer面试题26:树的子结构

题目描述:输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构) B是A的子结构, 即 A中有出现和B相同的结构和节点值。

示例 1: 输入:A = [1,2,3], B = [3,1]         输出:false
示例 2: 输入:A = [3,4,5,1,2], B = [4,1]   输出:true

解题思路:
若树 B 是树 A 的子结构,则子结构的根节点可能为树 A 的任意一个节点。因此,判断树 B 是否是树 A 的子结构,需完成以下两步工作:

1、先序遍历树 A 中的每个节点 n_A (对应函数 isSubStructure(A, B))
2、判断树 A 中 以 n_An为根节点的子树 是否包含树 B 。(对应函数 recur(A, B))

isSubStructure(A, B) 函数:

特例处理: 当 树 A 为空  树 B 为空 时,直接返回 false ;

isSubStructure 函数的返回值,若树 B 是树 A 的子结构,则必满足以下三种情况之一:

  1. 以 节点 A 为根节点的子树 包含树 B ,对应 dfs(A, B);
  2. 树 B 是 树 A 左子树 的子结构,对应 isSubStructure(A.left, B);
  3. 树 B 是 树 A 右子树 的子结构,对应 isSubStructure(A.right, B);

dfs 函数的终止条件:

  1. 当节点 B 为空:说明树 B 已匹配完成(越过叶子节点),因此返回 true ;
  2. 当节点 A 为空:说明已经越过树 A 叶子节点,即匹配失败,返回 false ;
  3. 当节点 A 和 B 的值不同:说明匹配失败,返回 false ;

返回值:

  1. 判断 A 和 B 的左子节点是否相等,即 dfs(A->left, B->left) ;
  2. 判断 A 和 B 的右子节点是否相等,即 dfs(A->right, B->right)
class Solution {
public:
    // 递归思想 大问题分解为小问题
    // 主函数用来确定根起点
    bool isSubStructure(TreeNode* A, TreeNode* B) 
    {        
        // 需要特判:
        // A === null,这说明 **从某一支出发到达叶子节点 ** 都无法找到一个节点与 B 的根相同,自然就不存在子结构
        // B === null,题目约定空树不是任意一个树的子结构
         if(B == NULL || A == NULL) return false;
        
        // dfs(A, B) 当前节点B是否是A的子树,若不是,则同理判断当前节点的孩子节点
        // 先从根节点开始,最特殊的情况,A与B 完全相同,那么通过短路运算,后续的 isSubStructure 便不会再递归执行
        // 若一开始的 dfs 为 false ,说明以当前根为起点的A并无与B相同的子结构
        // 需要递归,将根起点设为 A.left
        return dfs(A,B) || isSubStructure(A->left,B) || isSubStructure(A->right,B);
        
    }

    // 副函数用来确定结构是否相同
    bool dfs(TreeNode* A, TreeNode* B)
    {
        // 比较孩子节点时, B 可以为空, 例如[1]是[5,1]的子树 
        // 如果 B 树的所有点都被遍历完了(指的是到达 B 叶子下方),说明 A 中存在该子结构(或只是其中一枝)
        if(B == NULL) return true;
        // A 为空, B 不为空 B 一定不是 A 子树
        // 如果 B 树还有节点没被遍历而 A 已经遍历完了(指的是到达 A 叶子下方),说明 A 中不存在该子结构
        if(A == NULL) return false;

        // 若两个节点的值不同 则B不可能是A的子树 相同则比较两个节点的孩子节点是否相同
        return A->val == B->val && dfs(A->left,B->left) && dfs(A->right,B->right);
    }
};

 对称性递归(思路分析+解题模板+案例解读)

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

题型分类:
可以用对称性递归解决的二叉树问题大多是判断性问题(bool类型函数),这一类问题又可以分为以下两类:
1、不需要构造辅助函数。这一类题目有两种情况:第一种是单树问题,且不需要用到子树的某一部分(比如根节点左子树的右子树),只要利用根节点左右子树的对称性即可进行递归。第二种是双树问题,即本身题目要求比较两棵树,那么不需要构造新函数。该类型题目如下:
100. 相同的树
226. 翻转二叉树
104. 二叉树的最大深度
110. 平衡二叉树
543. 二叉树的直径
617. 合并二叉树
572. 另一个树的子树
965. 单值二叉树

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

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

if(!root) return true/false;
if(!root->left) return true/false/递归函数;
if(!root->right) return true/false/递归函数;

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

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

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

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

题目解读
空谈比较抽象,下面我们就对具体题目进行分析以及代码呈现
100. 相同的树
相同的树:比较两棵树是否相同
特殊判断:如果两棵树都是空树那么必然相同;如果两棵树其中只有一棵树为空树那么必不相同
返回值:两棵树都非空+根节点值相同+左子树相同+右子树相同
c++代码如下:

bool isSameTree(TreeNode*p, TreeNode*q)
{
    if (!p && !q)
        return true;
    return p && q && p->val == q->val && (isSameTree(p->left, q->left)) && (isSameTree(p->right, q->right));
}

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

int height(TreeNode*root)
{
    if (!root)
        return 0;
    else
        return max(height(root->left), height(root->right)) + 1;
}

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

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

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

bool isUnivalTree(TreeNode*root)
{
    if (!root) 
        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);
}

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

bool isSubtree(TreeNode*root1, TreeNode*root2)
{
    if (!root1 || !root2)
        return false;
    if (isSameTree(root1, root2))
        return true;
    return isSubtree(root1->left, root2) || isSubtree(root1->right, root2);
}

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

TreeNode*invertTree(TreeNode*root)
{
    if (!root)
        return nullptr;
    TreeNode*left = invertTree(root->left);
    TreeNode*right = invertTree(root->right);
    root->left = right;
    root->right = left;
    return root;
}


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

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

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

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

bool isMirror(TreeNode*p, TreeNode*q)
{
    if (!p && !q)
        return true;
    if (!p || !q)
        return false;
    return (p->val == q->val) && (isMirror(p->left, q->right)) && (isMirror(p->right, q->left));
}

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

class Solution {
    /*
    参考:数据结构与算法的题解比较好懂
    死死记住isSubStructure()的定义:判断B是否为A的子结构
    */
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        // 若A与B其中一个为空,立即返回false
        if(A == null || B == null) {
            return false;
        }
        // B为A的子结构有3种情况,满足任意一种即可:
        // 1.B的子结构起点为A的根节点,此时结果为recur(A,B)
        // 2.B的子结构起点隐藏在A的左子树中,而不是直接为A的根节点,此时结果为isSubStructure(A.left, B)
        // 3.B的子结构起点隐藏在A的右子树中,此时结果为isSubStructure(A.right, B)
        return recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
    }

    /*
    判断B是否为A的子结构,其中B子结构的起点为A的根节点
    */
    private boolean recur(TreeNode A, TreeNode B) {
        // 若B走完了,说明查找完毕,B为A的子结构
        if(B == null) {
            return true;
        }
        // 若B不为空并且A为空或者A与B的值不相等,直接可以判断B不是A的子结构
        if(A == null || A.val != B.val) {
            return false;
        }
        // 当A与B当前节点值相等,若要判断B为A的子结构
        // 还需要判断B的左子树是否为A左子树的子结构 && B的右子树是否为A右子树的子结构
        // 若两者都满足就说明B是A的子结构,并且该子结构以A根节点为起点
        return recur(A.left, B.left) && recur(A.right, B.right);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值