【算法17天:Day17】第六章二叉树 LeetCode 二叉树的所有路径(257)

91 阅读3分钟

题目二:

image.png

解法一:(递归)

解题思路:

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

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

前序遍历以及回溯的过程如图:

image.png

我们先使用递归的方式,来做前序遍历。要知道递归和回溯就是一家的,本题也需要回溯。

  1. 递归函数函数参数

要传入根节点,记录每一条路径的path,和存放结果集的result,结果集可以使用闭包放在外面,这里递归不需要返回值,代码如下:

let res = []
const getPath = function(node, curPath) {}
  1. 确定递归终止条件
if (cur === null) {
    return 

本题不能这样直接判断当前结点是否为空,因为本题要找到叶子结点,找到叶子结点就开始结束的处理逻辑了(把路径放进result)。为什么没有判断cur是否为空呢,因为内部的逻辑可以控制空节点不入循环。

因此,代码如下:

if (cur.left === null && cur.right === null) {
    // 终止处理逻辑
    curPath += cur.val
    res.push(curPath)
    return
}
  1. 确定单层递归逻辑

因为是前序遍历,需要先处理中间节点,中间节点就是我们要记录路径上的节点,先放进curPath中。

curPath += cur.val + '->'

注意:回溯和递归是一一对应的,有一个递归就要有一个回溯,所以回溯要和递归永远在一起,世界上最遥远的距离是你在花括号里,而我在花括号外!

但代码随想录中的C++代码能写出回溯,不知道JS中能不能写出来,应该是隐藏在递归调用了吧。

完整递归代码:

var binaryTreePaths = function(root) {
   //递归遍历+递归三部曲
   let res = [];
   //1. 确定递归函数 函数参数
   const getPath = function(node, curPath){
    //2. 确定终止条件,到叶子节点就终止
       if(node.left === null && node.right === null){
           curPath += node.val;
           res.push(curPath);
           return ;
       }
       //3. 确定单层递归逻辑
       curPath += node.val+'->';
       node.left && getPath(node.left, curPath);
       node.right && getPath(node.right, curPath);
   }
   getPath(root, '');
   return res;
};

Lettcode 官方讲解:

最直观的方法是使用深度优先搜索。在深度优先搜索遍历二叉树时,我们需要考虑当前的节点以及它的孩子节点。

如果当前节点不是叶子节点,则在当前的路径末尾添加该节点,并继续递归遍历该节点的每一个孩子节点。

如果当前节点是叶子节点,则在当前路径末尾添加该节点后我们就得到了一条从根节点到叶子节点的路径,将该路径加入到答案即可。

如此,当遍历完整棵二叉树以后我们就得到了所有从根节点到叶子节点的路径。当然,深度优先搜索也可以使用非递归的方式实现,这里不再赘述。

var binaryTreePaths = function(root) {
    const paths = [];
    const construct_paths = (root, path) => {
        if (root) {
            path += root.val.toString();
            if (root.left === null && root.right === null) { // 当前节点是叶子节点
                paths.push(path); // 把路径加入到答案中
            } else {
                path += "->"; // 当前节点不是叶子节点,继续递归遍历
                construct_paths(root.left, path);
                construct_paths(root.right, path);
            }
        }
    }
    construct_paths(root, "");
    return paths;
};

解法二:(迭代)

除了模拟递归需要一个栈,同时还需要一个栈来存放对应的遍历路径。

var binaryTreePaths = function(root) {
  if (!root) return [];
  const stack = [root], paths = [''], res = [];
  while (stack.length) {
    const node = stack.pop();
    let path = paths.pop();
    if (!node.left && !node.right) { // 到叶子节点终止, 添加路径到结果中
      res.push(path + node.val);
      continue;
    }
    path += node.val + '->';
    if (node.right) { // 右节点存在
      stack.push(node.right);
      paths.push(path);
    }
    if (node.left) { // 左节点存在
      stack.push(node.left);
      paths.push(path);
    }
  }
  return res;
};  

解法三:(广度优先遍历)

var binaryTreePaths = function(root) {
    const paths = [];
    if (root === null) {
        return paths;
    }
    const node_queue = [root];
    const path_queue = [root.val.toString()];

    while (node_queue.length) {
        const node = node_queue.shift(); 
        const path = path_queue.shift();

        if (node.left === null && node.right === null) {
            paths.push(path);
        } else {
            if (node.left !== null) {
                node_queue.push(node.left);
                path_queue.push(path + "->" + node.left.val.toString());
            }

            if (node.right !== null) {
                node_queue.push(node.right);
                path_queue.push(path + "->" + node.right.val.toString());
            }
        }
    }
    return paths;
};