二叉树所有路径求解(力扣257)

本文讲述了如何使用递归和回溯算法解决LeetCode题目257——二叉树的所有路径问题,通过前序遍历找到路径并使用StringBuilder高效拼接字符串。
摘要由CSDN通过智能技术生成


题目

Problem: 257. 二叉树的所有路径

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

前知

递归与回溯

递归是一种自然而然地用来处理树形结构的方法,因为树的结构本身就是递归的。在二叉树中,递归通常用于遍历和搜索。可以参考之前我写过的深度优先遍历中的递归法

        System.out.println(root.val);  // 先访问根节点
        preorderTraversal(root.left);   // 再递归遍历左子树
        preorderTraversal(root.right);  // 最后递归遍历右子树

回溯是一种解决问题的算法技巧,通常用于在问题的解空间中搜索所有可能的解。回溯算法通过尝试一系列的选择,并在每个选择后进行递归探索,直到找到解决方案或者确定无解为止。如果在某个选择后发现无法继续前进,就会回溯到之前的状态,尝试其他的选择。

回溯和递归是可以同时存在的,回溯通常是通过递归来实现的。在回溯算法中,递归函数的调用扮演了重要的角色,它负责在每个选择后继续搜索解空间。当达到某个条件时(例如找到解决方案或确定无解),递归会返回上一层,并尝试其他选择,这就是回溯的过程。回溯通常用于解决更复杂的问题,例如在二叉树中搜索特定路径、查找最优解或者n皇后问题等

StringBuilder

StringBuilder 是 Java 中用于处理可变字符序列的类,它提供了一系列方法来进行字符串的拼接、插入、删除等操作。与 String 类不同,StringBuilder 的对象是可变的,即可以在不创建新的对象的情况下直接修改其内容,因此在处理大量字符串拼接时,使用 StringBuilder 可以提高性能。
以下是常用的一些方法:

创建一个新的StringBuilder对象

StringBuilder sb = new StringBuilder();

append():在字符串末尾添加字符、字符数组、字符串、任意类型的数据等。
insert():在指定位置插入字符、字符数组、字符串、任意类型的数据等。
delete():删除指定位置的字符或字符区间。
replace():替换指定位置的字符或字符区间。
reverse():反转字符串中的字符顺序。
length():返回当前字符串的长度。
toString():将 StringBuilder 对象转换为 String 类型的字符串

字符串三者比较String,StringBuffer ,StringBuilder 。
如果需要频繁进行字符串操作且在单线程环境下,推荐使用 StringBuilder;如果在多线程环境下,推荐使用 StringBuffer;如果字符串内容不需要修改,可以使用 String。

  • 不可变性(Immutability):

String 是不可变的,一旦创建就无法修改其内容。每次对字符串进行操作(如拼接、替换等),都会产生一个新的字符串对象。
StringBufferStringBuilder 是可变的,可以在不创建新对象的情况下直接修改其内容。

  • 线程安全性(Thread Safety):

String 是线程安全的,因为它是不可变的,不会受到多线程并发访问的影响。
StringBuffer 的方法是线程安全的,因为它的方法都是同步的,可以在多线程环境下安全使用。
StringBuilder 不是线程安全的,因为它的方法不是同步的,如果在多线程环境下使用,需要额外的同步措施来保证线程安全。

  • 性能(Performance):

String 的不可变性使得它适合在不需要频繁修改字符串的情况下使用,但在大量字符串拼接的场景下,性能较差,因为每次拼接都会创建新的字符串对象。
StringBuffer 适用于在多线程环境下需要频繁进行字符串操作的场景,它的方法都是同步的,但性能相对较差。
StringBuilder 适用于在单线程环境下需要频繁进行字符串操作的场景,它的方法不是同步的,性能较 StringBuffer 更好。

  • 用途(Usage):

String 适用于需要不可变性的场景,例如字符串常量、字符串连接操作等。
StringBuffer 适用于多线程环境下需要频繁进行字符串操作的场景。
StringBuilder 适用于单线程环境下需要频繁进行字符串操作的场景,且性能要求较高的场景。


题解

一、思路

要求从根节点到叶子的路径,所以要用到递归的方式进行前序遍历找到一条路径,然后进行回溯来回退一个路径再进入到另一个路径

此图片取自Carl老师代码随想录里的
image.png

二、解题方法

递归三部曲

1. 确定递归函数的参数和返回值
通过前序递归先找到左树的叶子结点,参数为结点node,路径paths,最终结果result,不需要返回值

private void traversal(TreeNode root, List<Integer> paths, List<String> res)

2. 确定终止条件
终止条件不能是等到当前结点没有cur == NULL了才终止,要在到叶子结点,左右孩子都为空时就退出。因为这样才可以将路径添加到result数组里去,但是这样cur就没有判断是否为空,我们在下面第三步里判断。

终止处理的逻辑为用stringbuilder记录paths从根节点到叶子节点的数据并用“->”连接,将这个字符串添加到res里退出本次递归

		if (root.left == null && root.right == null) {
            // 输出
            StringBuilder sb = new StringBuilder();// StringBuilder用来拼接字符串,速度更快
            for (int i = 0; i < paths.size() - 1; i++) {
                sb.append(paths.get(i)).append("->");
            }
            sb.append(paths.get(paths.size() - 1));// 记录最后一个节点
            res.add(sb.toString());// 收集一个路径
            return;
        }

3. 确定单层递归的逻辑

因为前序遍历的顺序首先将中间节点加入路径里面,再递归左子树和右子树,上面第二步说了没有判断cur是否为空,于是在这里加上判断条件。由于递归和回溯是一一对应的,递归进了下一层的话,同样回溯也得有一个删除结点返回上一层的语句
paths.add(root.val);

if (root.left != null) { // 左
    traversal(root.left, paths, res);
    paths.remove(paths.size() - 1);// 回溯
}
if (root.right != null) { // 右
    traversal(root.right, paths, res);
    paths.remove(paths.size() - 1);// 回溯
}

三、Code

/**
 * 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> res = new ArrayList<>();// 存最终的结果
        if (root == null) {
            return res;
        }
        List<Integer> paths = new ArrayList<>();// 作为结果中的路径
        traversal(root, paths, res);
        return res;
    }

    private void traversal(TreeNode root, List<Integer> paths, List<String> res) {
        paths.add(root.val);// 前序遍历,中
        // 遇到叶子结点
        if (root.left == null && root.right == null) {
            // 输出
            StringBuilder sb = new StringBuilder();// StringBuilder用来拼接字符串,速度更快
            for (int i = 0; i < paths.size() - 1; i++) {
                sb.append(paths.get(i)).append("->");
            }
            sb.append(paths.get(paths.size() - 1));// 记录最后一个节点
            res.add(sb.toString());// 收集一个路径
            return;
        }
        // 递归和回溯是同时进行,所以要放在同一个花括号里
        if (root.left != null) { // 左
            traversal(root.left, paths, res);
            paths.remove(paths.size() - 1);// 回溯
        }
        if (root.right != null) { // 右
            traversal(root.right, paths, res);
            paths.remove(paths.size() - 1);// 回溯
        }
    }
}

总结

以上就是针对这道题的刷题笔记,讲解了怎么用递归以及回溯求解二叉树的所有路径问题,用前序遍历遇到叶子结点时则回溯到上面去重新遍历,还用到了stringbuilder来拼接字符串

  • 48
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
可以使用递归的方式来实现求解二叉树所有路径的功能。具体实现方法如下: 1. 定义一个函数,输入参数为当前节点指针、当前路径字符串和结果数组。 2. 如果当前节点为空,则直接返回。 3. 将当前节点的值添加到当前路径字符串中。 4. 如果当前节点节点,则将当前路径字符串添加到结果数组中。 5. 否则,递归遍历当前节点的左子树和右子树,分别调用上述函数。 6. 递归结束后,将当前节点的值从当前路径字符串中删除,以便遍历其它路径。 下面是具体的 C 代码实现: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_PATH_LEN 100 struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; }; void binaryTreePathsHelper(struct TreeNode* node, char* path, char** result, int* returnSize) { if (node == NULL) { return; } int len = strlen(path); if (len > ) { path[len] = '-'; path[len+1] = '>'; path[len+2] = '\'; } char val_str[10]; sprintf(val_str, "%d", node->val); strcat(path, val_str); if (node->left == NULL && node->right == NULL) { result[*returnSize] = (char*)malloc(sizeof(char) * (strlen(path) + 1)); strcpy(result[*returnSize], path); (*returnSize)++; } else { binaryTreePathsHelper(node->left, path, result, returnSize); binaryTreePathsHelper(node->right, path, result, returnSize); } path[strlen(path) - strlen(val_str) - 2] = '\'; } char ** binaryTreePaths(struct TreeNode* root, int* returnSize) { char** result = (char**)malloc(sizeof(char*) * MAX_PATH_LEN); *returnSize = ; char path[MAX_PATH_LEN]; path[] = '\'; binaryTreePathsHelper(root, path, result, returnSize); return result; } int main() { struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode)); root->val = 1; root->left = (struct TreeNode*)malloc(sizeof(struct TreeNode)); root->left->val = 2; root->left->left = NULL; root->left->right = (struct TreeNode*)malloc(sizeof(struct TreeNode)); root->left->right->val = 5; root->left->right->left = NULL; root->left->right->right = NULL; root->right = (struct TreeNode*)malloc(sizeof(struct TreeNode)); root->right->val = 3; root->right->left = NULL; root->right->right = NULL; int returnSize; char** result = binaryTreePaths(root, &returnSize); for (int i = ; i < returnSize; i++) { printf("%s\n", result[i]); } return ; } ``` 运行结果如下: ``` 1->2->5 1->3 ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值