二叉树基础知识力扣题构造二叉树总结

news2024/11/25 11:43:26

二叉树

如何理解二叉树,This is a question!

作者在去年被布置要求学习二叉树时对二叉树的理解并不是很深刻,甚至可以说是绕道走,但是Luck of the draw only draws the unlucky,在学期初考核时,作者三道二叉树题都没做出来,连最简单的创建都忘记了,当时想着提升,却拖到了现在;

这篇文章可以说是作者二叉树算法实战经验总结

基础知识

先来回顾一下二叉树的基本知识;但还是请读者曾经是见过二叉树的

一颗很普通的二叉树,他的定义是:是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成;

其余的基本知识,还是请自行搜索一下;现在来了解几种遍历手法

image-20230602232804296

二叉树的前序遍历

 int len;
//DLR
void DFS(int*arr,struct TreeNode*root) {
     if(root == NULL) return;
     arr[len] = root->val;
     len++;
     DFS(arr,root->left);
     DFS(arr,root->right);

 }

int* preorderTraversal(struct TreeNode* root, int* returnSize){
    int*arr = (int*)malloc(sizeof(int)*100);
    len = 0;
    if(root != NULL)
        DFS(arr,root);
    *returnSize = len;
    return arr;
}

二叉树的后序遍历

int len;
//LRD
void DFS(int*arr,struct TreeNode*root) {
     if(root == NULL) return;
     DFS(arr,root->left);
     DFS(arr,root->right);
     arr[len] = root->val;
     len++;

 }

int* postorderTraversal(struct TreeNode* root, int* returnSize){
   int*arr = (int*)malloc(sizeof(int)*100);
    len = 0;
    if(root != NULL)
        DFS(arr,root);
    *returnSize = len;
    return arr;
}

二叉树的中序遍历

int len;
//LDR
void DFS(int*arr,struct TreeNode* root) {
    if(root == NULL) return;
    DFS(arr,root->left);
    arr[len] = root->val;
    len++;
    DFS(arr,root->right);
}

int* inorderTraversal(struct TreeNode* root, int* returnSize){
    len = 0;
    int*arr = (int*)malloc(sizeof(int)*100);
    if(root != NULL) {
        DFS(arr,root);
    }
    *returnSize = len;
    return arr;
}

在三种遍历方法中,我们都采用了递归的方法,而仔细观察也会发现,这三段代码的区别,从字面上仅是递归函数的位置不同,也就是遍历时的顺序不同,这个也就是我们在解决二叉树算法题时要注意的点,不同的遍历方式对应着不同的类型的解决方案

在这三种遍历代码前,都标注了三个大写字母,D、L、R 分别代表遍历根结点、遍历左子树、遍历右子树,会发现字母的顺序也就代表着优先级也就是代码呈现的顺序;

如何理解这种遍历手法,以前序遍历为例,他的三个字母顺序为DLR,也就是根节点最优先,自然就是A,之后就到B,C这两个在这里判断的条件就成了谁左谁优先,也就是B先行,B的底下还有子树,坚持D最优先原则,由于他们有一个优秀的父结点,因为所有都可以得到先行一步的遍历权利,等到遍历完了也就是在轮到C和C的子树;

总结就是:坚持大方向,父结点优先,子结点也继承了优先权

那么基础知识就讲到这里,接下来实战演练

实战演练

104.二叉树的最大深度

这是一道非常基础的题目,也就是再问一根树枝最长能有多长,也就是遍历出奇迹

第一种方法:回溯

(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”

对这种算法的解释就是:一条道走到黑,且要走完每条道

先看代码:

class Solution {
    int depth = 0;
    int res = 0;

    public int maxDepth(TreeNode root) {
        traverse(root);
        return res;
    }

    void traverse(TreeNode root) {
        if (root == null) {
            return;
        }

        depth++;
        // 遍历的过程中记录最大深度
        res = Math.max(res, depth);
        traverse(root.left);
        traverse(root.right);
        depth--;
    }
}

对代码的唯一不理解的地方应该是为何要先depth++之后又depth--,我们可以把整个函数理解为一个在四处游走的图钉,当图钉到这个点上了,depth++,现在我小图钉在这里一条道走到黑要离开去其他地方了,那么则先depth--,因为回到更上面一层去了;这跟前序遍历很像

至于res的取值,只要放在depth--之前就行了

第二种方法:动态规划

class Solution2 {
    // 定义:输入一个节点,返回以该节点为根的二叉树的最大深度
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftMax = maxDepth(root.left);
        int rightMax = maxDepth(root.right);
        // 根据左右子树的最大深度推出原二叉树的最大深度
        return 1 + Math.max(leftMax, rightMax); //+1的原因:算上根节点
    }
}

代码很好理解,但是作者自己想不出来;思想很简单,也是遍历然后最后得出最大值,这个顺序相当于在后序遍历

543.二叉树的直径

其实这道题跟上面一道题非常像,作者对这道题的解读就是:从根节点出发第一长的树枝和第二长的树枝长度和;那么这道题也就迎刃而解了,只要在上一题的第二种解法种稍加修改,添加一个max记录值,便可以做出该题

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    int maxDiameter = 0;
    public int diameterOfBinaryTree(TreeNode root) {
        maxDepth(root);
        return maxDiameter;
    }
    
    int maxDepth(TreeNode root) {
        if(root == null) {
            return 0;
        }
        int leftMax = maxDepth(root.left);
        int rightMax = maxDepth(root.right);
        int myDiameter = leftMax + rightMax;
        maxDiameter = Math.max(myDiameter, maxDiameter);
        return 1 + (Math.max(leftMax, rightMax));
    }
}

116.填充每个节点的下一个右侧指针节点

这道题的神奇之处就在于,把不是一个父结点,但是在同一层的联系在了一起,那么只要把相对位置是left&right的都链接在一起,也就是多加一条执行语句traverse(node1.right, node2.left)即可

class Solution {
    public Node connect(Node root) {
        if(root == null) {
            return null;
        }
        traverse(root.left, root.right);
        return root;
    }
    void traverse(Node node1, Node node2) {
        if(node1 == null || node2 == null) {
            return;
        }
        node1.next = node2;
        traverse(node1.left, node1.right);
        traverse(node2.left, node2.right);
        traverse(node1.right, node2.left);
    }
}

105.从前序与中序遍历序列构造二叉树

它使用HashMap来存储中序遍历数组中值到索引的映射关系,这有助于确定根节点并将数组划分为子树以进行递归构建。
buildTree 方法接受前序遍历和中序遍历数组作为输入,并通过将中序遍历数组中的值和索引存储在 valToIndex HashMap 中来进行初始化。然后,它调用 build 方法,传递必要的参数来构建二叉树。
build 方法是一个递归函数,用于构建二叉树。它检查子树是否为空(preStart > preEnd),如果是则返回null。否则,它从前序遍历数组中确定根节点的值(preorder[preStart]),并找到它在中序遍历数组中的索引。然后,它计算左子树的大小(index - inStart),并先构造当前根节点。接下来,它递归构建左子树和右子树,并将它们连接到根节点。最后,它返回根节点

class Solution {
    // 存储 inorder 中值到索引的映射
    HashMap<Integer, Integer> valToIndex = new HashMap<>();

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        for (int i = 0; i < inorder.length; i++) {
            valToIndex.put(inorder[i], i);
        }
        return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
    }

    /*
       定义:前序遍历数组为 preorder[preStart..preEnd],
       中序遍历数组为 inorder[inStart..inEnd],
       构造这个二叉树并返回该二叉树的根节点
    */
    TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) {
        if (preStart > preEnd) {
            return null;
        }

        // root 节点对应的值就是前序遍历数组的第一个元素
        int rootVal = preorder[preStart];
        // rootVal 在中序遍历数组中的索引
        int index = valToIndex.get(rootVal);
        int leftSize = index - inStart;

        // 先构造出当前根节点
        TreeNode root = new TreeNode(rootVal);
        // 递归构造左右子树
        root.left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1);
        root.right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd);
        return root;
    }
}

106.从中序与后序遍历序列构造二叉树

class Solution {
    // 存储 inorder 中值到索引的映射
    HashMap<Integer, Integer> valToIndex = new HashMap<>();

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        for (int i = 0; i < inorder.length; i++) {
            valToIndex.put(inorder[i], i);
        }
        return build(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1);
    }

    /*
       定义:
       中序遍历数组为 inorder[inStart..inEnd],
       后序遍历数组为 postorder[postStart..postEnd],
       构造这个二叉树并返回该二叉树的根节点
    */
    TreeNode build(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd) {

        if (inStart > inEnd) {
            return null;
        }
        // root 节点对应的值就是后序遍历数组的最后一个元素
        int rootVal = postorder[postEnd];
        // rootVal 在中序遍历数组中的索引
        int index = valToIndex.get(rootVal);
        // 左子树的节点个数
        int leftSize = index - inStart;
        TreeNode root = new TreeNode(rootVal);
        // 递归构造左右子树
        root.left = build(inorder, inStart, index - 1, postorder, postStart, postStart + leftSize - 1);
        root.right = build(inorder, index + 1, inEnd, postorder, postStart + leftSize, postEnd - 1);
        return root;
    }
}

889.根据前序与后序遍历构造二叉树

class Solution {
    // 存储 postorder 中值到索引的映射
    HashMap<Integer, Integer> valToIndex = new HashMap<>();

    public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
        for (int i = 0; i < postorder.length; i++) {
            valToIndex.put(postorder[i], i);
        }
        return build(preorder, 0, preorder.length - 1, postorder, 0, postorder.length - 1);
    }

    // 定义:根据 preorder[preStart..preEnd] 和 postorder[postStart..postEnd]
    // 构建二叉树,并返回根节点。
    TreeNode build(int[] preorder, int preStart, int preEnd, int[] postorder, int postStart, int postEnd) {
        if (preStart > preEnd) {
            return null;
        }
        if (preStart == preEnd) {
            return new TreeNode(preorder[preStart]);
        }

        // root 节点对应的值就是前序遍历数组的第一个元素
        int rootVal = preorder[preStart];
        // root.left 的值是前序遍历第二个元素
        // 通过前序和后序遍历构造二叉树的关键在于通过左子树的根节点
        // 确定 preorder 和 postorder 中左右子树的元素区间
        int leftRootVal = preorder[preStart + 1];
        // leftRootVal 在后序遍历数组中的索引
        int index = valToIndex.get(leftRootVal);
        // 左子树的元素个数
        int leftSize = index - postStart + 1;

        // 先构造出当前根节点
        TreeNode root = new TreeNode(rootVal);
        // 递归构造左右子树
        // 根据左子树的根节点索引和元素个数推导左右子树的索引边界
        root.left = build(preorder, preStart + 1, preStart + leftSize, postorder, postStart, index);
        root.right = build(preorder, preStart + leftSize + 1, preEnd, postorder, index + 1, postEnd - 1);

        return root;
    }
}

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

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

相关文章

ArrayBlockingQueue中方法的基本使用

生产者生产数据 使用add()方法向队列中添加元素&#xff0c;在队列满的时候会抛出异常。 ArrayBlockingQueue是基于数组实现&#xff0c;初始化完成后长度是不可变的&#xff0c;在其构造方法中也都是有参构造&#xff0c;初始化对象时必须指定当前队列的长度。 使用offer()方…

day05——K-近邻算法

K-近邻算法 一、定义二、API三、实操&#xff1a;预测签到位置1&#xff0c;数据获取2&#xff0c;数据基本处理3&#xff0c;预测算法代码 四、调优1&#xff0c;什么是交叉验证2&#xff0c;超参数搜索-网格搜索(Grid Search)3&#xff0c;调优代码 五、KNN 算法总结 一、定义…

网工内推 | 应届生网工专场,最高15薪,有NP以上证书优先

01 智己汽车 &#x1f537;招聘岗位&#xff1a;网络工程师 &#x1f537;职责描述&#xff1a; 1.管理和运维支持网络基础设备&#xff08;防火墙&#xff0c;交换机&#xff0c;路由器&#xff0c;负载均衡、无线、准入等&#xff09;&#xff1b; 2.负责公司OA网络及公有云…

类和对象以及数组工具类的常用方法

文章目录 一、类和对象二、数组工具类的常用方法 一、类和对象 1、对象没人引用时&#xff0c;会被自动回收 2、对象一定在堆上&#xff0c;引用变量不一定在栈上 3、this表示当前对象的引用&#xff0c;谁调用eat方法(eat方法里有this)&#xff0c;谁就是this。this.data访问…

C++11 使用using定义别名(替代typedef)::作用域运算符

typedef 一切合法的变量的定义可以转换为类型 typedef unsigned int uint_t;示例如下&#xff1a; 使用 typedef 重定义类型是很方便的&#xff0c;但它也有一些限制&#xff0c;比如&#xff0c;无法重定义一个模板。 现在&#xff0c;在 C11 中终于出现了可以重定义一个模…

Kerberos从入门到精通以及案例实操系列(一)

1、Kerberos部署 1.1、Kerberos概述 1.1.1、什么是Kerberos Kerberos是一种计算机网络认证协议&#xff0c;用来在非安全网络中&#xff0c;对个人通信以安全的手段进行身份认证。这个词又指麻省理工学院为这个协议开发的一套计算机软件。软件设计上采用客户端/服务器结构&a…

STC89C52+DS18B20实现环境温度检测(数码管显示温度)

一、项目介绍 温度检测是工业自动化、生产线等众多领域中常见的应用场景之一,能及时准确地监测温度对于保障生产安全和提高生产效率有着非常重要的作用。而在现代的电子制造行业中,使用单片机和传感器等电子元器件进行温度检测已经成为了一个比较成熟的技术方案。 本项目选…

Qcom_hexagon编译自动获取目录和特定文件的方法

一&#xff0c;简介 本文主要介绍&#xff0c;如何在高通hexagon ide中的hexagon.min中添加获取目录和.c文件的方法&#xff0c;供参考。 二&#xff0c;具体命令 OBJ_PATH : ./awinic_sp_module/algo_libINCLUDE_PATH : $(shell find $(OBJ_PATH ) -type d) SRC_C_FILE : …

synchronized 的底层原理

tip: 作为程序员一定学习编程之道&#xff0c;一定要对代码的编写有追求&#xff0c;不能实现就完事了。我们应该让自己写的代码更加优雅&#xff0c;即使这会费时费力。 文章目录 一、synchronized 的底层原理二、synchronized 的锁升级原理1、偏向锁2、轻量级锁3、重量级锁 一…

大幅提升iOS编译速度的cocoapods二进制化插件介绍

1. 背景 驾校一点通iOS项目是采用是cocoapods来管理组件的&#xff0c;又经过多年的组件化发展&#xff0c;目前组件已经达到了120的数量。在这种组件规模下&#xff0c;主工程的打包时间也从最开始的几分钟增加到十几分钟&#xff08;M1&#xff09;、二十几分钟&#xff08;…

restTemplate转发Https请求

代码架构 package com.http.controller;import com.http.RestTemplateConfig; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework…

Vercel部署个人博客

vercel 部署静态资源网站极其方便简单&#xff0c;并且有可观的访问速度&#xff0c;最主要的是免费部署。 如果你还没有尝试的话&#xff0c;强烈建议去使用一下。 演示博客演示http://202271.xyz/?vercel vercel 介绍 注册账号 进入Vercel官网https://vercel.com&#x…

Android studio安装教程(图文详解,简单搞定)

一 下载 根据自己计算机选择对应版本点击下载 https://developer.android.google.cn/studio 二 安装Android Studio Android Studio 是Google提供的一个Android开发环境&#xff0c;基于IntelliJ IDEA类似 Eclipse ADT&#xff0c;他集成了Android 所需的开发工具。需要注意…

RocketMq的集群的搭建(2主2从异步复制集群模式)

一 RocketMq集群搭建 1.1 说明 本案例采用2m-2s-async的方式搭建集群。 实际项目中&#xff0c;为了达到高可用&#xff0c;一般会使用dleger。 https://blog.csdn.net/wssc63262/article/details/126003507 1.2 集群规划说明 集群规划说明&#xff1a; nameserver是一个…

机器学习——集成学习(装袋法Bagging、提升法Boosting、梯度提升决策树GBDT、随机森林RF)

集成学习 集成学习通过构建并结合多个学习器来完成学习任务 集成方法是用多种学习方法的组合来获取比原方法更优的结果 使用于组合的算法是弱学习算法 即分类正确率仅比随机猜测略高的学习算法 但是组合之后的效果仍可能高于强学习算法 即集成之后的算法准确率和效率都很高…

# 车载软件架构 —— 闲聊几句AUTOSAR OS(三)

我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 没有人关注你。也无需有人关注你。你必须承认自己的价值,你不能站在他人的角度来反对自己。人生在世,最怕的就是把别人的眼光当成自己生活的唯一标准。到最…

程序员从0到收获心仪offer,我靠训练营实现了180度逆袭!

我相信&#xff0c;在未来的职场中&#xff0c;我也能通过这段时间养成的学习习惯和生活习惯让自己一步步成为更好的自己&#xff0c;以自己为荣 我在大学里主修计算机科学与技术&#xff0c;一个普通的院校&#xff0c;一个算是常见的专业&#xff0c;我知道我的学历和一些其他…

Tomcat的部署(贼详细)

目录 一、Tomcat服务器简介 1、Tomcat服务器 2、Tomcat三大核心组件 3、 Java Servlet 4、JSP全称Java Server Pages 5、 Tomcat 功能组件结构 6、 Container 结构分析 7、Tomcat 请求过程 二&#xff1a;Tomcat部署与安装 1.关闭防火墙&#xff0c;上传所需软件包 2.安…

前端、后端工程师学习路线

学习的平台推荐 视频平台&#xff1a; 慕课&#xff1a;http://www.imooc.com/ 腾讯课堂&#xff1a;https://ke.qq.com/ 教程平台 菜鸟&#xff1a;https://www.runoob.com/ W3&#xff1a;https://www.w3school.com.cn/ yibai&#xff1a;https://www.yiibai.com/ 前端学习路…

c++—STL(六大组件)

一、STL概述 1. STL概述 &#xff08;1&#xff09;STL定义&#xff1a;STL&#xff08;standard template library&#xff09;&#xff0c;标准模板库&#xff0c;是一个高效的c程序库&#xff0c;重在提高了代码的复用性&#xff1b;主要包含了常用的数据结构和基本算法&am…