剑指 Offer 26树的子结构(相关话题:对称性递归,在线算法)

news2024/11/27 20:58:24

目录

开篇引言

题目描述

代码实现

题目拓展

拓展解读

一类

100. 相同的树

226. 翻转二叉树

104. 二叉树的最大深度

110. 平衡二叉树

543. 二叉树的直径

617. 合并二叉树

572. 另一个树的子树

965. 单值二叉树

二类

101. 对称二叉树

解题总结


开篇引言

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

题目描述

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

例如:
给定的树 A:

     3
    / \
   4   5
  / \
 1   2

给定的树 B:

   4 
  /
 1

返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

示例 1:

输入:A = [1,2,3], B = [3,1]
输出:false

示例 2:

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

代码实现

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
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);
    }
}

题目拓展

 可以用对称性递归解决的二叉树问题大多是判断性问题(bool类型函数),这一类问题又可以分为以下两类:

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

100. 相同的树

226. 翻转二叉树

104. 二叉树的最大深度

110. 平衡二叉树

543. 二叉树的直径

617. 合并二叉树

572. 另一个树的子树

965. 单值二叉树

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

101. 对称二叉树 

剑指 Offer 26. 树的子结构

拓展解读

一类

100. 相同的树

public boolean isSameTree(TreeNode p, TreeNode q) {
 
      
       if(p==q && p==null){
           return true;
       }
       if(p==null && q!=null){
           return false;
       }
       if(p!=null && q==null){
           return false;
       }

       TreeNode lleft =   p.left;
       TreeNode lright =  p.right;

       TreeNode rleft =   q.left;
       TreeNode rright =  q.right;

       if(p.val!=q.val){
           return false;
       }
 

       return isSameTree(lleft,rleft) && isSameTree(lright,rright);
     
}

226. 翻转二叉树

public TreeNode invertTree(TreeNode root) {

		if(root==null) {
			return null;
		}
		//下面三句是将当前节点的左右子树交换
		TreeNode tmp = root.right;
		root.right = root.left;
		root.left = tmp;

        invertTree(root.left);
        invertTree(root.right);

        return root;
}

104. 二叉树的最大深度

public int maxDepth(TreeNode root) {

        int leftDepth = 0;
        int rightDepth = 0;
        if(root==null){
           return 0;
        }
        if(root.left==null && root.right==null){
            return 1;
        }

        if(root.left!=null){
             leftDepth =   maxDepth(root.left)+1;
        }
        if(root.right!=null){
             rightDepth =   maxDepth(root.right)+1;
        }

        return Math.max(leftDepth,rightDepth);

}

110. 平衡二叉树

 public boolean isBalanced(TreeNode root) {

    if(root==null){
        return true;
    }
  
      return Math.abs(getHeght(root.left)-getHeght(root.right))<2 && isBalanced(root.left) && isBalanced(root.right);
   
   
}


public int getHeght(TreeNode root){

       if(root==null){
           return 0;
       }

        return Math.max(getHeght(root.left),getHeght(root.right))+1;
}

543. 二叉树的直径

int diameter;

public int diameterOfBinaryTree(TreeNode root) {
    diameter = 0;
    traverse(root);
    return diameter;
}

// 返回树的深度
int traverse(TreeNode root) {
    if (root == null) {
        return 0;
    }

    int left = traverse(root.left); // 左子树的深度
    int right = traverse(root.right); // 右子树的深度
    // 直接访问全局变量
    diameter = Math.max(diameter, left + right);
    return 1 + Math.max(left, right);
}

在这道题中,全局变量计算的是路径的最大值(max)。计算 max 的方式不是一次性求出来的,而是在二叉树遍历的过程中,每出现一个值,就把这个值和全局变量比较计算,算一个最大值。最终全局变量能得到全局的最大值。

实际上这利用了 max 的性质,max 是一种在线算法。简单来说,在线算法就是在计算的时候,所有的输入数据以“流”的形式一个个进来,算法每次只处理一条数据,不需要保存全部的数据。

除了 max 之外,sum、all 也都属于在线算法(all 指的是 x1 && x2 && ... && xn 这样的计算)。可以举几个其他的二叉树题目例子:

二叉树的坡度:563. Binary Tree Tilt(sum)


public int findTilt(TreeNode root) {

     if(root==null){
         return 0;
     }

      int leftSum =  getSum(root.left);
      int rightSum  =    getSum(root.right);

      root.val=  Math.abs(leftSum  -rightSum);

      return root.val + findTilt(root.left) + findTilt(root.right);
} 

public int getSum(TreeNode root){

        if(root==null){
            return 0;
        }

        return root.val + getSum(root.left) + getSum(root.right);
}

判断平衡二叉树:110. Balanced Binary Tree(all)
二叉树路径数字:129. Sum Root to Leaf Numbers(sum)

617. 合并二叉树

把root1作为返回树

public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {

        if(root1==null && root2==null){

           return root1;

        }else if((root1==null && root2!=null) || (root1!=null && root2==null)){

            if(root1==null){
                root1 =new TreeNode(root2.val);
                root1.left = mergeTrees(null,root2.left);
                root1.right = mergeTrees(null,root2.right);
            }else{
                root1.left = mergeTrees(root1.left,null);
                root1.right = mergeTrees(root1.right,null);
            }

        }else{
            root1.val= root1.val + root2.val;
            root1.left = mergeTrees(root1.left,root2.left);
            root1.right = mergeTrees(root1.right,root2.right);
        }

         return root1;
}

返回一个新的树,可以简化代码

public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        if (t1 == null) {
            return t2;
        }
        if (t2 == null) {
            return t1;
        }
        TreeNode merged = new TreeNode(t1.val + t2.val);
        merged.left = mergeTrees(t1.left, t2.left);
        merged.right = mergeTrees(t1.right, t2.right);
        return merged;
}

572. 另一个树的子树

注意子树和子结构的区别:

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

 

 代码可以在本文主题目的基础上删稍作修改就可以使用

965. 单值二叉树

int univalVal=-1;

public boolean isUnivalTree(TreeNode root) {

       Boolean flag = true;
       if(root!=null){
            if(univalVal==-1){
                univalVal = root.val;
            }
            if(root.val!=univalVal){
                 flag =false;
            }

            return flag && isUnivalTree(root.left) && isUnivalTree(root.right);
       }

       return true;
 
      
}

二类

101. 对称二叉树

public boolean isSymmetric(TreeNode root) {
		if(root==null) {
			return true;
		}
		//调用递归函数,比较左节点,右节点
		return dfs(root.left,root.right);
}
	
boolean dfs(TreeNode left, TreeNode right) {
		//递归的终止条件是两个节点都为空
		//或者两个节点中有一个为空
		//或者两个节点的值不相等
		if(left==null && right==null) {
			return true;
		}
		if(left==null || right==null) {
			return false;
		}
		if(left.val!=right.val) {
			return false;
		}
		//再递归的比较 左节点的左孩子 和 右节点的右孩子
		//以及比较  左节点的右孩子 和 右节点的左孩子
		return dfs(left.left,right.right) && dfs(left.right,right.left);
}

解题总结

  1. 一般来说思维越奇特代码越简洁,官方题解一般代表了“最优代码”,普通的思维不一定可以马上领会,可以退而求其次再优化“思维”简化代码。
  2. 在练习中插入两个以上的主题或技能,也是一种胜过集中练习的学 习方法
  3. 与集中练习相比,穿插练习与多样化练习的一个显著优点是,它们有助于我们更好地学习如何评估背景,以及辨识问题间的差异,从一系列可选的答案中选择并应用正确的解决方案。
  4. 人们顽固地相信,自己把心思放在一件事上,拼命重复就能学得更好,认为这些观点经受住了时间的考验,而且“练习,练习,再练习”的明显收效再次证明了这种方法的好处。但是,科学家们把习得技能阶段的这种成绩称为“暂时的优势”,并把它同“潜在的习惯优势”区分开来。形成习惯优势有种种技巧,例如有间隔的练习、有穿插内容的练习,引出努力的动力。 及多样化练习,这些技巧恰恰会放缓有明显成果的学习进程,它们不会在练习中提高我们的表现。我们从表面上看不到成绩提高,也就没有付出的动力
  5. 心智模型可以 被调整,可以在复杂多变的环境中发挥作用。专业的表现,源自在不同 
    环境下、在专长领域进行的数千小时的练习。通过这些练习,你可以积 累大量类似的心智模型,从而保证自己在特定环境下做出正确分析,立 刻挑选出正确的应对方案并加以执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/153356.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue引入并使用Element-UI组件库的两种方式

前言 在开发的时候&#xff0c;虽然我们可以自己写css或者js甚至一些动画特效&#xff0c;但是也有很多开源的组件库帮我们写好了。我们只需要下载并引入即可。 vue和element-ui在开发中是比较般配的&#xff0c;也是我们开发中用的很多的&#xff0c;下面就介绍下如何在eue项…

【力扣/牛客刷题】二叉树篇

作者&#xff1a;✿✿ xxxflower. ✿✿ 博客主页&#xff1a;xxxflower的博客 专栏&#xff1a;【力扣、牛客刷题】篇 语录&#xff1a;⭐每一个不曾起舞的日子&#xff0c;都是对生命的辜负。⭐ 文章目录100. 相同的树572. 另一棵树的子树226. 翻转二叉树平衡二叉树101.对称二…

C++11之后的decltype类型指示符

C11之后的decltype类型指示符一、什么是decltype类型指示符二、typeid运算符三、使用decltype指示符四、decltype和引用五、decltype(auto)六、本章代码汇总一、什么是decltype类型指示符 有时会遇到这种情况&#xff1a;希望从表达式的类型推断出要定义的变量的类型&#xff…

深度对比学习综述

本文综合考察了对比学习近年的发展和进步, 提出一种新的面向对比学习的归类方法, 并基于提出的归类方法, 对现有对比研究成果进行系统综述, 并评述代表性方法的技术特点和区别, 系统对比分析现有对比学习方法在不同基准数据集上的性能表现。 摘要 在深度学习中, 如何利用大量、…

Linux - 目录与文件操作

目录1.操作目录1.1 目录切换1.2 浏览目录1.3 目录创建1.4 目录删除1.5 复制目录1.6 移动或重命名目录2. 操作文件2.1 查找文件2.2 查看文件信息2.3 查看文件内容2.4 创建文件2.5 文件修改-vim2.6 删除文件2.7 复制和重命名文件3. 文件或目录进行压缩或解压3.1 压缩3.2 解压1.操…

链路追踪工具之Zipkin

Zipkin是一个分布式跟踪系统&#xff0c;Zipkin的设计是基于谷歌的Google Dapper论文&#xff0c;它可以帮助收集时间数据&#xff0c;在microservice架构下&#xff0c;通过链路追踪&#xff0c;可以便捷的分析服务调用延迟问题。每个应用程序向Zipkin server端报告数据&#…

【高光谱、多光谱和全色图像融合】

HyperFusion: A Computational Approach for Hyperspectral, Multispectral, and Panchromatic Image Fusion &#xff08;超融合&#xff1a;高光谱、多光谱和全色图像融合的计算方法&#xff09; 高空间分辨率的高光谱图像&#xff08;HSI&#xff09;和多光谱图像&#xff…

链表热门面试题(二)

目录前言一、删除链表的倒数第 N 个结点二、两两交换链表中的节点三、旋转链表四、删除排序链表中的重复元素五、删除排序链表中的重复元素 II六、反转链表II七、删除链表中的节点八、奇偶链表前言 一、删除链表的倒数第 N 个结点 题目&#xff1a; 方法1&#xff1a;找到删除…

【Java多线程】创建多线程方式一

线程的创建和启动 *Java语言的JVM允许程序运行多个线程&#xff0c;它通过java.lang.Thread类来体现。 *Thread类的特性 每个线程都是通过某个特定Thread对象的run()方法来完成操作的&#xff0c;经常 把run()方法的主体称为线程体 通过该Thread对象的start()方法来启动这个…

判断两条线段是否相交

参考链接&#xff1a; 1 2 一、判断线段是否相交需要下面两步&#xff1a; &#xff08;1&#xff09;快速排斥实验 &#xff08;2&#xff09;跨立实验 二、第一步快速排斥实验 对上图两条L1,L2线段来说&#xff0c;L1 x的最大值为d端点x5&#xff0c;L2 x的最小值为a端点x…

RobotFramework环境安装和入门

环境安装1、安装python建议版本3.7&#xff0c;Robot Framework不支持python最新版。 可以通过以下地址下载https://www.python.org/ftp/python/3.7.3/python-3.7.3-amd64.exe安装过程中选择添加到环境变量。2、安装Robot Framework以及所需组件注意各个组件的版本号&#xff0…

LVGL学习笔记15 - 文本框TextArea

目录 1. Parts 2. 样式 2.1 修改背景bg 2.2 修改边界border 2.3 修改文本 2.3.1 lv_textarea_add_char 2.3.2 lv_textarea_add_text 2.3.3 lv_textarea_set_text 2.3.4 设定可输入的字符 2.3.5 删除字符 2.3.6 设定字符串最大长度 2.4 滚动条 2.4.1 滚动方向 2.4.2 …

RHCE学习笔记-133-1

RH133-01 installation 光盘引导后,在提示符下输入:linux askmethod 可以支持:local cdrom,hard drive,nfs image,ftp,http方式 cpu on x86 2个物理超线程CPU 使用smp or hugemem kernel可以支持 32个物理cpus 内存 标准x86 kernel 4G smp i686/athlon kernel 16G hugemem SMP …

创建新分支,基于某一分支创建新分支,git push --set-upstream origin

背景&#xff1a; 基于hcc1.2的开发分支拉去hcc_hotfix_1.2.0分支&#xff0c;解读&#xff1a;在仓库中基于某个分支&#xff0c;创建一个新的分支 做法&#xff08;解决&#xff09;&#xff1a; 1、第一步&#xff1a;git branch hcc_hotfix_1.2.0 2、第二步&#xff1a;g…

葡萄城邀您参与「表格技术开发者新年分享会」,共话表格技术新未来!

数据资产是企业最重要的资产之一&#xff0c;表格也是企业最普遍的数据整理手段。在数字经济时代&#xff0c;各行业飞速发展&#xff0c;拥抱数字化、进行数字化升级已成为大势所趋&#xff0c;企业对协同办公、大数据分析、表格智能化应用等领域的需求也呈爆发式增长&#xf…

GrapeCity Documents Image Viewer JavaScript Crack

GrapeCity Documents Image Viewer 是跨平台 JavaScript 图像查看器 允许用户使用我们的 JavaScript 图像查看器在您的 Web 应用程序中查看和编辑图像。采集 by Ω578867473 支持多种图像格式 适用于所有现代浏览器和框架&#xff0c;包括 Edge、Chrome、Firefox、Opera、Saf…

【UE4 第一人称射击游戏】33-创建一个小地图

上一篇&#xff1a;【UE4 第一人称射击游戏】32-添加击杀AI的提示功能&#xff08;使用到控件蓝图的动画功能&#xff09;本篇效果&#xff1a;可以看到左上角完成了小地图的制作步骤&#xff1a;打开名为“FPSHUD”的控件蓝图&#xff0c;拖入一个图像控件&#xff0c;表示迷你…

Diffusion 和Stable Diffusion的数学和工作原理详细解释

扩散模型的兴起可以被视为人工智能生成艺术领域最近取得突破的主要因素。而稳定扩散模型的发展使得我们可以通过一个文本提示轻松地创建美妙的艺术插图。所以在本文中&#xff0c;我将解释它们是如何工作的。 扩散模型 Diffusion 扩散模型的训练可以分为两部分: 正向扩散→在图…

输出全排列(C++版)

目录 输出全排列 一、问题描述 二、运行环境说明 ​三、代码段 四、效果展示 输出全排列 备注&#xff1a;大二&#xff08;上&#xff09;数据结构课程设计A题 一、问题描述 请编…

我是如何利用cps平台赚钱的?

你好&#xff0c;我是你们熟悉而又陌生的好朋友梦龙&#xff0c;一个创业期的年轻人 今天跟你做个分享&#xff0c;众所周知互联网是一块非常大的蛋糕&#xff0c;几位互联网巨头也做不到完全吃透&#xff0c;这次梦龙给你分享的是实际的经验&#xff0c;实际的案例分享。 最开…