【LeetCode刷题日记】递归与回溯实战 257.二叉树的所有路径——一篇文章彻底搞懂回溯

🔥个人主页:北极的代码(欢迎来访)

🎬作者简介:java后端学习者

❄️个人专栏:苍穹外卖日记SSM框架深入JavaWeb

命运的结局尽可永在,不屈的挑战却不可须臾或缺!

前言:

大家好,我是代码不加冰,今天我们来学习二叉树相关的算法题,具体是关于二叉树的路径问题,其实也就是递归的问题,我们后面要学的回溯的实现通常就是使用递归来实现的,所以还是很重要的,让我们来具体看看吧。

摘要:

本文探讨了二叉树路径问题的递归解法。题目要求返回所有从根节点到叶子节点的路径,通过前序遍历和回溯算法实现。核心思路是:使用path列表记录当前路径,遇到叶子节点时将路径转为字符串存入result;递归访问左右子树后,通过path.remove()实现回溯。文章详细解析了递归函数各环节的实现逻辑,包括路径记录、叶子节点判断、递归调用和回溯操作,并提供了完整的Java代码实现。该方法有效解决了二叉树路径遍历问题,时间复杂度为O(n),空间复杂度为O(h),其中h为树的高度。

题目背景:

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

叶子节点 是指没有子节点的节点。

示例 1:

复制代码
输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]

示例 2:

复制代码
输入:root = [1]
输出:["1"]

提示:

  • 树中节点的数目在范围 [1, 100]
  • -100 <= Node.val <= 100

题目解析:

根据题目分析,并不是简单的遍历问题,我们需要返回所有路径,因此即便我们走过了之前的节点,也要回去,看那个节点还有没有别的路径,这叫回溯(听着挺高大上的),其实就是跟开车差不多,在一个路口有多条路,我们在路口这个节点可以选择一个,但是之后还要回到路口,把剩下的路一条条的走完。

核心要点
要点 说明
遍历方式 前序遍历(根 → 左 → 右)
何时记录 遇到叶子节点(左右都为空)
何时回溯 离开当前节点时,从路径中删除它
路径表示 List<String>StringBuilder 维护
具体的实现例子:

需要注意:

  • 前进:负责加节点、构建路径

  • 回溯:负责删节点、清理现场

  • 路径是维护在一个全局(或传参)的列表里的

  • 回溯删除的是 当前节点

  • 根节点不会被删除,除非整个递归全部结束

根节点一直保留,是因为还没从根节点回溯回去,我们回溯每次只回到上一个节点,但是题目要求返回的是从根节点到所有子节点的完整路径,对于一开始刚学回溯的同学来说可能容易混淆,错误的认为回溯是回到第一个节点,然后从头开始,如果真是这样,可能会导致后面的路径丢失,遍历不完整个树。

理解 认为回溯 后果
❌ 错误 回到根节点,从头开始 路径永远只有一条,永远走不完所有组合
✅ 正确 回到上一个节点,继续尝试 路径逐步构建、撤销,覆盖所有可能性

理解完这些,那么这题就很简单了,仅仅是代码实现的问题了。

在二叉树路径问题中,通常有两个容器:

容器 作用 存储内容 示例
result 存储最终结果 所有路径字符串 ["1->2->5", "1->3"]
path 存储当前正在走的路径 当前路径的节点值 [1,2,5]
  • path临时工 :不断变化,记录当前走到哪了,不断变化,回溯删除

  • result永久存储:一旦记录一条完整路径,就永久保存

第1段:主方法

java

复制代码
public List<String> binaryTreePaths(TreeNode root) {
    List<String> result = new ArrayList<>();
    if (root == null) return result;
    
    List<Integer> path = new ArrayList<>();
    dfs(root, path, result);
    return result;
}

解释

  • result:最终要返回的结果,装所有路径字符串

  • 如果树是空的,直接返回空列表(题目要求)

  • path:临时记录当前走的路径(比如 [1,2,5]

  • 调用 dfs 开始深度优先搜索

第2段:递归函数开头------做选择

java

复制代码
private void dfs(TreeNode node, List<Integer> path, List<String> result) {
    path.add(node.val);

解释

  • 每进入一个节点,就把它的值加入 path

  • 例如从节点1进入节点2:path[1] 变成 [1,2]

这就是"做选择"------把当前节点纳入当前路径

第3段:叶子节点判断

java

复制代码
if (node.left == null && node.right == null) {
    result.add(buildPath(path));
}

解释

  • 如果当前节点没有左孩子也没有右孩子 → 说明是叶子节点

  • 一条完整的路径已经形成(从根到当前叶子)

  • 调用 buildPath[1,2,5] 转成 "1->2->5"

  • 加入 result

示例 :走到节点5时,path = [1,2,5],记录下 "1->2->5"

第4段:非叶子节点------继续递归

java

复制代码
else {
    if (node.left != null) dfs(node.left, path, result);
    if (node.right != null) dfs(node.right, path, result);
}

解释

  • 如果不是叶子,说明还可以往下走

  • 有左孩子就递归左子树

  • 有右孩子就递归右子树

  • 注意:递归完成后会回到这里,继续往下执行

第5段:回溯------撤销选择

java

复制代码
path.remove(path.size() - 1);

解释

  • 这是最容易被忽略的一行

  • 当前节点的左右子树都探索完了,要离开这个节点

  • 离开之前,必须把它从 path 中删除

  • 这样返回上一层时,path 还是原来的样子

示例

  • 进入5之前:path = [1,2]

  • 进入5:path = [1,2,5]

  • 记录完路径,离开5:path = [1,2] ← 恢复原样

  • 这样节点2才能继续去探索其他孩子

第6段:格式化方法

java

复制代码
private String buildPath(List<Integer> path) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < path.size(); i++) {
        if (i > 0) sb.append("->");
        sb.append(path.get(i));
    }
    return sb.toString();
}

解释

  • [1,2,5] 转成 "1->2->5"

  • StringBuilder 效率更高

  • if (i > 0) 确保第一个数字前面不加 ->


题目答案:

java 复制代码
/**
 * 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 {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> result=new ArrayList<>();
        if(root==null){
            return result;}
        List<Integer> path=new ArrayList<>();
          dfs(root, path, result);
        return result;
    
    }
    private void dfs(TreeNode node,List<Integer>path,List<String> result){
        path.add(node.val);
            if (node.left == null && node.right == null) {
            result.add(buildPath(path));
        } else {
            // 3. 继续递归探索子节点
            if (node.left != null) dfs(node.left, path, result);
            if (node.right != null) dfs(node.right, path, result);
        }
          path.remove(path.size() - 1);

    }
    private String buildPath(List<Integer> path){
        StringBuilder sb=new StringBuilder();
        for(int i=0;i<path.size();i++){
            if(i>0) sb.append("->");
            sb.append(path.get(i));
        }
        return sb.toString();
    }
}

结语:如果对你有帮助,请**点赞,关注,收藏,**你的支持就是我最大的鼓励!

相关推荐
weixin_459753941 小时前
mysql如何批量重置数据库用户密码_MySQL批量修改密码Shell脚本
jvm·数据库·python
Gofarlic_OMS1 小时前
Mastercam浮动许可利用率低:软件许可浪费,回收再分配
java·大数据·开发语言·架构·制造
ulias2121 小时前
leetcode热题 - 7
数据结构·算法·leetcode
AC赳赳老秦1 小时前
OpenClaw与飞书多维表格联动:自动同步工作数据、生成统计图表,实现高效管理
java·数据库·python·信息可视化·飞书·deepseek·openclaw
吃好睡好便好1 小时前
在Matlab中用sphere( )函数绘制球面图
开发语言·前端·javascript·学习·算法·matlab·信息可视化
图码1 小时前
矩阵中的“对角线强迫症”:如何优雅地判断Toeplitz矩阵?
数据结构·c++·线性代数·算法·青少年编程·矩阵
lynnlovemin1 小时前
二分查找与二分答案算法详解(基于C++实现)
c语言·开发语言·算法·二分查找·二分答案
sichuanwww1 小时前
python中的websockets简单样例
python·websocket·asyncio·异步操作
瑞行AI1 小时前
一套数据格式框架搞定大模型微调和对齐训练
算法·语言模型