二叉树路径问题+递归+有关题目

news2024/9/23 11:19:34

一、分类

1、自顶向下

顾名思义,就是从某一个节点(不一定是根节点),从上向下寻找路径,到某一个节点(不一定是叶节点)结束,具体题目如下:而继续细分的话还可以分成一般路径与给定和的路径

  • 二叉树的所有路径
  • 面试题 04.12. 求和路径
    1. 路径总和
    1. 路径总和 II
  • 437 路径总和 III
  • 988 从叶结点开始的最小字符串

2、非自顶向下:

就是从任意节点到任意节点的路径,不需要自顶向下
124. 二叉树中的最大路径和
125. 最长同值路径
126. 二叉树的直径

二、关于递归

  1. 回溯和递归式一一对应的。回溯的目的是path 不能一直加入节点,它还要删节点,然后才能加入新的节点。
  2. 递归函数的返回值问题
  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(113.路径总和ii)
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (236. 二叉树的最近公共祖先 )
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(112.路径总和)

三、解题模板

这类题通常用深度优先搜索(DFS)和广度优先搜索(BFS)解决。所以掌握二叉树的遍历思想很重要。

1、自顶而下

一般路径

// 定义一个数组,用于存储遍历二叉树时走过的所有路径
let res = []

// 遍历二叉树,一般而言,就是使用DFS或者是BFS
// 遍历的时候,每进入一个分支,需要将当前节点的值放入path中,path代表是每条路径。
// 当一个分支结束也就是单层递归结束的时候,将path放入最后的res
var dfs = function(root,path){
    if(!root) return  // 节点为空,直接返回
	// 处理根节点
    path.push(root.val) 
    
    //单个分支结束递归条件,节点root为叶子节点
    if(!root.left && !root.right){
        res.push(path)
        return
    }
    
    // 节点root为非叶节点,检查左右节点继续遍历
    // 此时会一直向左走,即先遍历完左子树
    if(root.left){
    	dfs(root.left,path)
    	path.pop() // 回溯
    }
    // 左子树结束,此时还在倒数第二节点的递归内,所以此时是回溯了
    if(root.right){
    	dfs(root.right,path)
    	path.pop() // 回溯
    } 
}
dfs(root,[])
// 返回最后的结果数组,即所有的路径
return res

给定和的路径

// 定义一个数组,用于存储遍历二叉树时满足条件的路径
let res = []

// 遍历二叉树,一般而言,就是使用DFS或者是BFS
// 遍历的时候,每进入一个分支,需要将当前节点的值放入path中,path代表是每条路径。并且需要将目标值sum-节点当前值。
// 当一个分支结束也就是单层递归结束的时候,检查sum是否为0
var dfs = function(root,sum,path){
    if(!root) return  // 节点为空,直接返回

    // 每遍历到一个节点的逻辑
    path.push(root.val) // 做出选择
    sum -= root.val

    //单个分支结束递归条件:节点root为叶子节点
    // 检查到满足条件的路径:sum此时为0
    if(!root.left && !root.right && sum == 0){
        res.push(path)
        return 
    }

    // 节点root为非叶节点,继续遍历左右子树
    if(root.left){
    	dfs(root.left,sum,path)
    	path.pop() // 回溯
    }
    if(root.right){
    	dfs(root.right,sum,path)
    	path.pop() // 回溯
    }
}
dfs(root,sum,path)
// 返回最后的结果数组
return res

2、非自顶而下

思想:设计一个辅助函数maxpath,调用自身求出以一个节点为根节点的左侧最长路径left和右侧最长路径right,那么经过该节点的最长路径就是left+right。每次不断更新全局变量即可。
注意:

  • left,right代表的含义要根据题目所求设置,比如最长路径、最大路径和等等
let res=0;
// 递归函数需要返回值
var maxPath = function(root) //以root为路径起始点的最长路径
{	
	// 单层递归结束条件
    if (!root) return 0;
    // 单层逻辑
    int left=maxPath(root.left);
    int right=maxPath(root.right);
    res = max(res, left + right + root.val); //更新全局变量  	
    // 单层递归逻辑的返回值
    return max(left, right);   //返回左右路径较长者
}

maxPath(root)
return res

三、题目

257. 二叉树的所有路径

在这里插入图片描述

使用模板1。递归函数不需要返回值。

var binaryTreePaths = function(root) {  
    // res是最后的结果数组,不参与遍历
    let res = [] 

    // 1. 确定递归函数 函数参数。这里的递归函数不需要返回值。
    var dfs = function (root,path){
        if(!root) return 
        path += String(root.val)

        // 2. 确定终止条件,叶子节点,将单分支路径path放入res,并且结束单分支路径
        if(!root.left && !root.right){
            res.push(path)
            return // 单层递归结束
        }

        // 3. 确定单层递归逻辑,非叶节点
        if(root.left){
            dfs(root.left,path+ '->')
        }
        if(root.right){
            dfs(root.right,path+ '->')
        }
    }
   
    dfs(root,"")
    return res
};

其实这个题目是运行了回溯的,回溯就隐藏在 dfs(root.right,path+ ‘->’);中的 path + “->”。上面的代码实际上是下面的代码。
当最左边的分支结束,即叶子节点7递归结束。回到了11节点的递归中,此时检查11的右节点是否存在。

if (cur->left) {
    path += "->";
    traversal(cur->left, path, result); // 左
    path.pop_back(); // 回溯 '>'
    path.pop_back(); // 回溯 '-'
}
if (cur->right) {
    path += "->";
    traversal(cur->right, path, result); // 右
    path.pop_back(); // 回溯 '>' 
    path.pop_back(); //  回溯 '-' 
}

112. 路径总和

使用模板2,这个题目不是返回路径,而是判断是否存在。注意这个递归函数是需要返回值的,即遇到合适的路径立刻返回,不需要遍历整棵树。
在这里插入图片描述

var hasPathSum = function(root, targetSum) {
	// 1、不需要返回值
    var dfs = function(root,reSum){  
		// 2、终止条件
        // 叶子节点,且和为sum
        if(!root.left && !root.right && reSum== 0){
            return true
        }
        // 叶子节点,和不为sum
         if(!root.left && !root.right && reSum!= 0){
            return false
        }
              
        // 3、单层递归逻辑,需要返回值
        // 遇到叶子节点返回true,则直接返回true
        if(root.left && hasPathSum(root.left,reSum-root.left.val)){
            return true
        }
        if(root.right && hasPathSum(root.right,reSum-root.right.val)){
            return true
        }
        // 其他情况就是fasle
        return false
    }
    
    if(!root) return false
    // 需要返回值
    return dfs(root,targetSum-root.val)
};

以上代码中是包含着回溯的,没有回溯,如何后撤重新找另一条路径呢。

回溯隐藏在(root.right,reSum-root.right.val)这里, 因为把reSum-root.right.val直接作为参数传进去,函数结束,reSum的数值没有改变。

if (cur->left) { // 左
    count -= cur->left->val; // 递归,处理节点;
    if (traversal(cur->left, count)) return true;
    count += cur->left->val; // 回溯,撤销处理结果
}
if (cur->right) { // 右
    count -= cur->right->val;
    if (traversal(cur->right, count)) return true;
    count += cur->right->val;
}
return false;

113. 路径总和 II

使用模板2

var pathSum = function(root, targetSum) {
    let res = [],path = [];

    // 1、递归函数,不需要返回值
    var dfs = function(root,reSum,path){
        // 2、分支递归结束条件 叶子节点
        if(!root.left && !root.right && reSum== 0){
            // 深拷贝
            res.push([...path])
            return  // 结束单分支
        }
         if(!root.left && !root.right && reSum!= 0) return;
      
        // 非叶
        if(root.left){
       		// 存值
        	path.push(root.left.val)
            dfs(root.left,reSum-root.left.val,path)   // 这里也有回溯的思想,参考上一题
            // 一直向左之后,需要回溯,进入下一个分支
            path.pop() // 回溯,将存入的值弹出
        }
        if(root.right){
        	path.push(root.right.val)
            dfs(root.right,reSum-root.right.val,path)   
            path.pop()
        }
        // 其他
		return;
    }
	if(!root) return res;
	// 把根节点提前放入路径
    dfs(root,targetSum-root.val,[root.val])
    return res
};

实际上的两处回溯:


        if (cur->left) { // 左 (空节点不遍历)
            path.push_back(cur->left->val);
            count -= cur->left->val;
            traversal(cur->left, count);    // 递归
            count += cur->left->val;        // 回溯
            path.pop_back();                // 回溯
        }
        if (cur->right) { // 右 (空节点不遍历)
            path.push_back(cur->right->val);
            count -= cur->right->val;
            traversal(cur->right, count);   // 递归
            count += cur->right->val;       // 回溯
            path.pop_back();                // 回溯
        }

543. 二叉树的直径

使用模板3,求出的是路径的长度最大值
整个二叉树路径最长 = 左子树最长+右子树最长,所以使用递归的思想。

var diameterOfBinaryTree = function(root) {
    // res统计路径
    let res = 0

    // 整个二叉树路径最长 = 左子树最长+右子树最长
    var maxDepth = function(root){
        // 递归结束条件
        if(!root) return 0
        
        // 单层递归逻辑
        // 左最长
        let left = maxDepth(root.left)
        // 右最长
        let right = maxDepth(root.right)
        // 更新全局变量res
        res = Math.max(left+right,res)
        
        // 返回值
        return Math.max(left,right)+1
    }
    maxDepth(root)
    return res
};

124. 二叉树中的最大路径和

使用模板3,求出的是路径上节点的和的最大值.

var maxPathSum = function(root) {
    // 最大路径和,res为负无穷,因为节点值可能为负数
    // 全局变量res的初值设置是0还是-Infinity要看题目节点是否存在负值,如果存在就用-Infinity,否则就是0
    let res = -Infinity

    // 递归函数
    var dfs = function(root){
        // 单层递归结束条件
        if(!root) return 0

        // 左右子树的和最大值,节点值可能为负数
        let left = Math.max(dfs(root.left),0)
        let right = Math.max(dfs(root.right),0)

        // 更新res
        res = Math.max(left+right+root.val,res)

        // 单层逻辑返回值
        return Math.max(left,right)+root.val
    }

    dfs(root)
    return res
};

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

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

相关文章

Qt 利用UDP进行通信

一、UDP的特点 UDP(用户数据报协议)是一种简单轻量级、不可靠、面向数据报,无连接的传输层协议。而TCP/IP协议却是有连接的 二、UDP适合应用的几种情况 1、网络数据大多为短消息 2、拥有大量客户端 3、对数据安全性无特殊要求 4、网络负…

pmap gdb 分析堆外内存泄露情况

一、查看内存分部 pmap -x 8 | sort -k3 -n -r | more ---- 8 是 PID 最大的肯定是堆内存。 其他的就需要看情况来分析了。 二、cat /proc/8/smaps | grep 7fad64000000 -- 8 是 PID , 地址的前4个0需要去掉。查到起止内存地址。 7fad64000000-7fad68000000 r…

Bioinformatics2019 | FP2VEC+:基于新分子特征的分子性质预测

论文标题:FP2VEC:a new molecular featurizer for learning molecular properties 代码: GitHub - wsjeon92/FP2VEC 预测化合物性质最成功的方法之一是定量结构-活性关系(QSAR)方法。 Mol2vec使用分子子结构表将分子结构表示为类似于分子指…

甘露糖-聚乙二醇-CY5 Cy5-PEG-mannose

甘露糖-聚乙二醇-CY5 Cy5-PEG-mannose 中文名称:甘露糖-菁染料CY5 英文名称:mannose-Cyanine5 别称:CY5标记甘露糖,CY5-甘露糖 存储条件:-20C,避光,避湿 外观:固体或粘性液体,取…

设计模式之美——实战MVC的意义

对于一个工程师来说,如果要追求长远发展,你就不能一直只把自己放在执行者的角色,不能只是一个代码实现者,你还要有独立负责一个系统的能力,能端到端(end to end)开发一个完整的系统。这其中的工…

《机械工程基础》复习题

一、填空题: 1. 构件由于受力不同,会产生不同的变形。基本形式有以下五种:1. 弯曲 ;2. 扭转 ; 3. 剪切 ;4. 轴向拉伸 ;5. 轴向压缩 。 2. 在机器中,运动的基本单元称之为__机构_ ___…

ip-guard安全网关问题集锦一

1、忘记安全网关Web管理界面的登录密码如何处理? 重置安全网关Web管理界面的登录密码操作如下: 1、把网线连接电脑和网管的emp端口。 2、修改电脑的Ip为190.190.190.x,子网掩码:255.255.0.0。 3、电脑上访问http://190.190.190.1…

Django Web框架的使用

1.前言 Django是基于Python的重量级开源Web框架。Django拥有高度定制的ORM和大量的API,简单灵活的视图编写、优雅的url、适用于快速开发的模板以及强大的管理后台。 Django简介可以查看菜鸟教程 Django 简介 | 菜鸟教程 2.使用pip安装Django 当我们更换镜像源进…

Windows更新NVIDIA显卡驱动

笔记本显卡联想拯救者Y70001050Ti 1、首先进入GeForce官网,选择顶部的驱动程序。 2、拉到下面手动搜素驱动程序,有以下6个筛选条件。 3、我的显卡是GTX 1050Ti,所以选择的配置情况如下。这里注意下产品系列选择的一定要是带(NoteB…

一款可以协助排查视频是否乱序的软件:BitRecoverFree JPEG Viewer

笔者在做某个嵌入式linux视频项目的过程中,遇到的需求如下:同事在解码进程中将h264解码为RGB数据,发送给Qt进程,我在Qt进程中通过RPC接收RGB图片数据(至于为啥不是接收压缩后的h264数据,这是历史遗留问题&a…

虾皮、Lazada怎么选爆款?测评有哪些方法?

对于一个店铺来说,想要让自己店铺获得更多的流量推广,那么打造出爆款是非常重要的,虾皮、Lazada作为跨境平台也是同样如此,下面就来介绍虾皮、Lazada怎么去选爆款? 1、跟卖 以Shopee、Lazada平台选品,直接“抄”同行…

JavaScript开发工具WebStorm入门教程:开始运行WebStorm(一)

WebStorm是一个JavaScript开发工具,用于JavaScript及其相关技术编码,包括TypeScript、React、Vue、Angular、Node.js、HTML和样式表。就像IntelliJ IDEA和其他JetBrains ide一样,WebStorm让您的开发体验更愉快,自动化日常工作&…

SpringBoot集成Spring Data JPA项目实操

《从零打造项目》系列文章 工具 比MyBatis Generator更强大的代码生成器 ORM框架选型 SpringBoot项目基础设施搭建 SpringBoot集成Mybatis项目实操 SpringBoot集成MybatisPlus项目实操 SpringBoot集成Spring Data JPA项目实操 前言 该说的在《SpringBoot集成Mybatis项目实…

HikariCP源码阅读笔记

加入HikariCP的maven依赖 <dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId><version>4.0.3</version> </dependency> <dependency><groupId>mysql</groupId><artifactId&g…

[附源码]java毕业设计疫情背景下社区公共卫生服务系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

10月BIOTREE协助发表文章再创新高,最高影响因子31.373

10月&#xff0c;BIOTREE在代谢组学、蛋白质组学、多组学方向共收录24篇客户文章&#xff0c;总影响因子&#xff1a;183.749&#xff0c;最高影响因子&#xff1a;31.373&#xff0c;IF>5分有19篇&#xff0c;发表杂志有Cell Metabolism、Clinical Immunology、Cell and Bi…

web网页设计期末课程大作业 HTML+CSS+JavaScript重庆火锅(代码质量好)

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

5、设计模式之工厂模式

工厂模式5.1 案例引入5.2简单工厂模式5.2.1 简单工厂模式结构5.2.2 实现5.2.3 优缺点分析5.3 工厂方法模式5.3.1 工厂方法模式结构5.3.2 新需求5.3.3 思路5.3.4 实现5.3.5 优缺点分析5.4 抽象工厂5.4.1 介绍5.4.2 结构5.4.3 实现5.4.4 使用场景5.5 工厂配置文件实现解耦相关文章…

【Vue】pc和移动端网页样式适配

在下面环节会讲解怎么做pc和移动端网页样式适配。 在当下有两种实现样式适配的&#xff1a;JS 适配方案和CSS 媒体查询适配。下面会具体讲解一下代码该怎么写。 &#x1f64f; 希望该文章能帮助到你。 1. JS 适配方案 比如在src/router/index.vue文件中有一个统一的Layout组件包…

《机器学习实战》9.树回归

目录 树回归 1 复杂数据的局部性建模 2 连续和离散型特征的树的构建 3 将CART算法用于回归 3.1 构建树 3.2 运行代码 4 树剪枝 4.1 预剪枝 4.2 后剪枝 5 模型树 6 示例&#xff1a;树回归与标准回归的比较 7 使用python的Tkinter库创建GUI 7.1 用Tkinter创建GUI …