Skip to content

[113] 路径总和 II

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

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

示例 1:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22

输出:[[5,4,11,2],[5,8,4,5]]

示例 2:

输入:root = [1,2,3], targetSum = 5

输出:[]

示例 3:

输入:root = [1,2], targetSum = 0

输出:[]

这道题是路径总和系列的第二题,相比 [112] 路径总和只需要判断是否存在,本题要求收集所有满足条件的完整路径,因此需要在 DFS 遍历时维护当前路径,并在到达叶子节点且路径和满足条件时记录结果。

核心思路是回溯:在前序位置将当前节点加入路径、扣减目标值,然后递归左右子树,最后在后序位置将节点从路径中移除,恢复状态。

需要注意的是,path 是引用类型(数组),所有递归层共享同一个数组,因此必须在后序位置手动 pop 来回溯。而 rest 是值类型(number),每层递归有自己的副本,无需手动恢复。

ts
function pathSum(root: TreeNode | null, targetSum: number): number[][] {
  const res: number[][] = [];
  if (!root) return res;

  function helper(node: TreeNode, path: number[], rest: number) {
    // 前序位置:做选择
    path.push(node.val);
    rest -= node.val;

    // 到达叶子节点且路径和满足条件,收集结果
    if (!node.left && !node.right && rest === 0) {
      res.push([...path]);
    }

    node.left && helper(node.left, path, rest);
    node.right && helper(node.right, path, rest);

    // 后序位置:撤销选择(回溯)
    path.pop();
  }

  helper(root, [], targetSum);
  return res;
}

本题也可以使用迭代方式实现。迭代时所有状态(pathrest)都只有一份,全局共享,因此 pathrest 都需要手动恢复。

另外,由于 path.push 发生在前序位置,path.pop 发生在后序位置——节点必须在左右子树都遍历完之后才能从路径中移除——因此迭代版本需要使用后序遍历的模板,通过 prev 指针判断右子树是否已经访问过,从而确定何时可以安全回溯。

ts
function pathSum(root: TreeNode | null, targetSum: number): number[][] {
  const res: number[][] = [];
  if (!root) return res;

  const stack: TreeNode[] = [];
  let node: TreeNode | null = root;
  const path: number[] = [];
  let rest: number = targetSum;
  let prev: TreeNode | null = null;

  while (node || stack.length) {
    while (node) {
      // 前序位置:做选择
      path.push(node.val);
      rest -= node.val;

      if (!node.left && !node.right && rest === 0) {
        res.push([...path]);
      }

      stack.push(node);
      node = node.left;
    }

    const curr = stack[stack.length - 1];

    if (curr.right && prev !== curr.right) {
      // 右子树还没访问过,先去右子树
      node = curr.right;
    } else {
      // 后序位置:左右子树都遍历完了,安全回溯
      stack.pop();
      path.pop();
      rest += curr.val;
      prev = curr;
    }
  }

  return res;
}

MIT LICENSE