7.<tag-二叉树和回溯>lt.257. 二叉树的所有路径

lt.257. 二叉树的所有路径

[案例需求]

在这里插入图片描述

[思路分析一, 递归法]

这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。

在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一一个路径在进入另一个路径。

前序遍历以及回溯的过程如图:
在这里插入图片描述
我们先使用递归的方式,来做前序遍历。要知道递归和回溯就是一家的,本题也需要回溯。

递归三部曲

  1. 递归函数参数及返回值
    • 要传入根节点, 记录每一条路径的path, 和存放结果集的res, 这里递归不需要返回值:
//root--> 被看做是根节点的当前节点

void traversal(TreeNode root, List<Integer> paths, List<String> res)
  1. 确定递归终止条件

在这里插入图片描述

if(root.left != null &&& root.right != null){
	终止时的处理逻辑
}

为什么没有判断root是否为空呢,因为下面的逻辑可以控制空节点不入循环。

再来看一下终止处理的逻辑。

这里
- 使用List<Integer> 的paths来记录路径,所以要把用StringBuilder拼接paths中的节点,然后转换为String放进 认识里。
- res时List<String>
那么为什么使用了List 结构来记录路径呢? 因为在下面处理单层递归逻辑的时候,要做回溯,使用List方便来做回溯。

可能有的同学问了,我看有些人的代码也没有回溯啊。

其实是有回溯的,只不过隐藏在函数调用时的参数赋值里,下文我还会提到。

这里我们先使用List<Integer> 结构的path容器来记录路径,那么终止处理逻辑如下:

if(root.left == null && root.right == null){ //当前节点是叶子节点
	List<Integer> paths;
	StringBuilder sb = new StringBuilder();
	
	//这里我们单独留下最后一个节点, 后面处理
	// 因为中间节点都要在节点后加上"->"
	// 其实也可以对这个paths使用 String.join("->", paths);
	for(int i = 0; i < paths.size() - 1; i++){ //将paths中记录的节点拼接起来
		sb.append(paths.get(i)).append("->");
	}
	sb.appen(paths.get(paths.size() - 1); //拼接paths中的最后一个节点
	res.add(sb);   // 结果集合中添加一条路径
	return;
}
  1. 确定单层递归逻辑
  • 因为是前序遍历, 需要先处理根节点, 根节点就是我们要记录路径上的节点, 遇到了就放入到paths中.
paths.add(root.val);  // paths记录单条路径节点的list集合, root.val是被看做是根节点的当前节点.
  • 然后是递归和回溯的过程,上面说过没有判断root是否为空,那么在这里递归的时候,如果为空就不进行下一层递归了。

所以递归前要加上判断语句,下面要递归的节点是否为空,如下:

if(root.left != null){
	traversal(root.left, paths, res);
}

if(root.right != null){
	traversal(root.right, paths, res);
}

此时还没完,递归完,要做回溯啊,因为path 不能一直加入节点,它还要删节点,然后才能加入新的节点。

那么回溯要怎么回溯呢,一些同学会这么写,如下:

if(root.left != null){
	traversal(root.left, paths, res);
}

if(root.right != null){
	traversal(root.right, paths, res);
}

paths.remove(paths.get(paths.size() - 1);

这个回溯就要很大的问题,我们知道,回溯和递归是一一对应的,有一个递归,就要有一个回溯,这么写的话相当于把递归和回溯拆开了, 一个在花括号里,一个在花括号外。

所以回溯要和递归永远在一起,

这意味着, 每一次递归访问一个节点的时候, 在访问过之后, 就要把这个节点从paths中去除掉, 避免影响到别的节点的遍历

那么代码应该这么写:

if(root.left != null){
	traversal(root.left, paths, res);
	paths.remove(paths.get(paths.size() - 1);
}

if(root.right != null){
	traversal(root.right, paths, res);
	paths.remove(paths.get(paths.size() - 1);
}

所以最终递归实现的代码如下所示:

[代码实现]

/**
 * 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<>();
        List<Integer> paths = new ArrayList<>();

        traverse(root, paths, res);

        return res;
    }

    //1. 递归函数, 中序遍历整棵树的所有节点
    //参数: paths记录每一个子路径上的所有节点
    // res, 记录每一条路径
    public void traverse(TreeNode root, List<Integer> paths, List<String>res){
        //2. 递归结束条件
       if(root == null)return;

        //3. 单层递归逻辑
        paths.add(root.val);
        StringBuilder sb = new StringBuilder(); //用来拼接节点

        if(root.left == null && root.right == null){
          

            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());
        }

         我们把这个单层的逻辑放在上面,把往下遍历左子树, 右子树放在下面, 
        // 其实就是前序遍历的过程
        if(root.left != null){
            traverse(root.left, paths, res);
            paths.remove(paths.size() - 1);
        } 

        if(root.right != null){
            traverse(root.right, paths, res);
            paths.remove(paths.size() - 1);
        }
    }
}

在这里插入图片描述

[思路分析二, 迭代法]

在这里插入图片描述

在这里插入图片描述

[代码实现一, 只用一个队列存储结点和路径字符串]

//2. 迭代法
class Solution {
    /**
     * 迭代法
     */
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> result = new ArrayList<>();
        if (root == null)
            return result;
        Stack<Object> stack = new Stack<>();
        // 节点和路径同时入栈
        stack.push(root);
        stack.push(root.val + "");
        while (!stack.isEmpty()) {
            // 节点和路径同时出栈
            String path = (String) stack.pop();
            TreeNode node = (TreeNode) stack.pop();
            // 若找到叶子节点
            if (node.left == null && node.right == null) {
                result.add(path);
            }
            //右子节点不为空
            if (node.right != null) {
                stack.push(node.right);
                stack.push(new StringBuilder().append(path).append("->").append(node.right.val).toString());
            }
            //左子节点不为空
            if (node.left != null) {
                stack.push(node.left);
                stack.push(new StringBuilder().append(path).append("->").append(node.left.val).toString());
            }
        }
        return result;
    }
}

[代码实现二, 用两个队列分别存储结点和路径字符串]

//3. 迭代法

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> paths = new ArrayList<String>();
        if (root == null) {
            return paths;
        }
        Queue<TreeNode> nodeQueue = new LinkedList<TreeNode>();
        Queue<String> pathQueue = new LinkedList<String>();

        nodeQueue.offer(root);
        pathQueue.offer(Integer.toString(root.val));

        while (!nodeQueue.isEmpty()) {
            TreeNode node = nodeQueue.poll(); 
            String path = pathQueue.poll();

            if (node.left == null && node.right == null) {
                paths.add(path);
            } else {
                if (node.left != null) {
                    nodeQueue.offer(node.left);
                    pathQueue.offer(new StringBuffer(path).append("->").append(node.left.val).toString());
                }

                if (node.right != null) {
                    nodeQueue.offer(node.right);
                    pathQueue.offer(new StringBuffer(path).append("->").append(node.right.val).toString());
                }
            }
        }
        return paths;
    }
}
posted @   青松城  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示