顾名思义,就是从某一个节点(不一定是根节点),从上向下寻找路径,到某一个节点(不一定是叶节点)结束,具体题目如下:而继续细分的话还可以分成一般路径与给定和的路径
就是从任意节点到任意节点的路径,不需要自顶向下
124. 二叉树中的最大路径和
125. 最长同值路径
126. 二叉树的直径
这类题通常用深度优先搜索(DFS)和广度优先搜索(BFS)解决。所以掌握二叉树的遍历思想很重要。
// 定义一个数组,用于存储遍历二叉树时走过的所有路径
let res = []// 遍历二叉树,一般而言,就是使用DFS或者是BFS
// 遍历的时候,每进入一个分支,需要将当前节点的值放入path中,path代表是每条路径。
// 当一个分支结束也就是单层递归结束的时候,将path放入最后的res
var dfs = function(root,path){if(!root) return // 节点为空,直接返回// 处理根节点path.push(root.val) //单个分支结束递归条件,节点root为叶子节点if(!root.left && !root.right){res.push(path)return}// 节点root为非叶节点,检查左右节点继续遍历// 此时会一直向左走,即先遍历完左子树if(root.left){dfs(root.left,path)path.pop() // 回溯}// 左子树结束,此时还在倒数第二节点的递归内,所以此时是回溯了if(root.right){dfs(root.right,path)path.pop() // 回溯}
}
dfs(root,[])
// 返回最后的结果数组,即所有的路径
return res
// 定义一个数组,用于存储遍历二叉树时满足条件的路径
let res = []// 遍历二叉树,一般而言,就是使用DFS或者是BFS
// 遍历的时候,每进入一个分支,需要将当前节点的值放入path中,path代表是每条路径。并且需要将目标值sum-节点当前值。
// 当一个分支结束也就是单层递归结束的时候,检查sum是否为0
var dfs = function(root,sum,path){if(!root) return // 节点为空,直接返回// 每遍历到一个节点的逻辑path.push(root.val) // 做出选择sum -= root.val//单个分支结束递归条件:节点root为叶子节点// 检查到满足条件的路径:sum此时为0if(!root.left && !root.right && sum == 0){res.push(path)return }// 节点root为非叶节点,继续遍历左右子树if(root.left){dfs(root.left,sum,path)path.pop() // 回溯}if(root.right){dfs(root.right,sum,path)path.pop() // 回溯}
}
dfs(root,sum,path)
// 返回最后的结果数组
return res
思想:设计一个辅助函数maxpath,调用自身求出以一个节点为根节点的左侧最长路径left和右侧最长路径right,那么经过该节点的最长路径就是left+right。每次不断更新全局变量即可。
注意:
let res=0;
// 递归函数需要返回值
var maxPath = function(root) //以root为路径起始点的最长路径
{ // 单层递归结束条件if (!root) return 0;// 单层逻辑int left=maxPath(root.left);int right=maxPath(root.right);res = max(res, left + right + root.val); //更新全局变量 // 单层递归逻辑的返回值return max(left, right); //返回左右路径较长者
}maxPath(root)
return res

使用模板1。递归函数不需要返回值。
var binaryTreePaths = function(root) { // res是最后的结果数组,不参与遍历let res = [] // 1. 确定递归函数 函数参数。这里的递归函数不需要返回值。var dfs = function (root,path){if(!root) return path += String(root.val)// 2. 确定终止条件,叶子节点,将单分支路径path放入res,并且结束单分支路径if(!root.left && !root.right){res.push(path)return // 单层递归结束}// 3. 确定单层递归逻辑,非叶节点if(root.left){dfs(root.left,path+ '->')}if(root.right){dfs(root.right,path+ '->')}}dfs(root,"")return res
};
其实这个题目是运行了回溯的,回溯就隐藏在 dfs(root.right,path+ ‘->’);中的 path + “->”。上面的代码实际上是下面的代码。
当最左边的分支结束,即叶子节点7递归结束。回到了11节点的递归中,此时检查11的右节点是否存在。
if (cur->left) {path += "->";traversal(cur->left, path, result); // 左path.pop_back(); // 回溯 '>'path.pop_back(); // 回溯 '-'
}
if (cur->right) {path += "->";traversal(cur->right, path, result); // 右path.pop_back(); // 回溯 '>' path.pop_back(); // 回溯 '-'
}
使用模板2,这个题目不是返回路径,而是判断是否存在。注意这个递归函数是需要返回值的,即遇到合适的路径立刻返回,不需要遍历整棵树。

var hasPathSum = function(root, targetSum) {// 1、不需要返回值var dfs = function(root,reSum){ // 2、终止条件// 叶子节点,且和为sumif(!root.left && !root.right && reSum== 0){return true}// 叶子节点,和不为sumif(!root.left && !root.right && reSum!= 0){return false}// 3、单层递归逻辑,需要返回值// 遇到叶子节点返回true,则直接返回trueif(root.left && hasPathSum(root.left,reSum-root.left.val)){return true}if(root.right && hasPathSum(root.right,reSum-root.right.val)){return true}// 其他情况就是faslereturn false}if(!root) return false// 需要返回值return dfs(root,targetSum-root.val)
};
以上代码中是包含着回溯的,没有回溯,如何后撤重新找另一条路径呢。
回溯隐藏在(root.right,reSum-root.right.val)这里, 因为把reSum-root.right.val直接作为参数传进去,函数结束,reSum的数值没有改变。
if (cur->left) { // 左count -= cur->left->val; // 递归,处理节点;if (traversal(cur->left, count)) return true;count += cur->left->val; // 回溯,撤销处理结果
}
if (cur->right) { // 右count -= cur->right->val;if (traversal(cur->right, count)) return true;count += cur->right->val;
}
return false;
使用模板2
var pathSum = function(root, targetSum) {let res = [],path = [];// 1、递归函数,不需要返回值var dfs = function(root,reSum,path){// 2、分支递归结束条件 叶子节点if(!root.left && !root.right && reSum== 0){// 深拷贝res.push([...path])return // 结束单分支}if(!root.left && !root.right && reSum!= 0) return;// 非叶if(root.left){// 存值path.push(root.left.val)dfs(root.left,reSum-root.left.val,path) // 这里也有回溯的思想,参考上一题// 一直向左之后,需要回溯,进入下一个分支path.pop() // 回溯,将存入的值弹出}if(root.right){path.push(root.right.val)dfs(root.right,reSum-root.right.val,path) path.pop()}// 其他return;}if(!root) return res;// 把根节点提前放入路径dfs(root,targetSum-root.val,[root.val])return res
};
实际上的两处回溯:
if (cur->left) { // 左 (空节点不遍历)path.push_back(cur->left->val);count -= cur->left->val;traversal(cur->left, count); // 递归count += cur->left->val; // 回溯path.pop_back(); // 回溯}if (cur->right) { // 右 (空节点不遍历)path.push_back(cur->right->val);count -= cur->right->val;traversal(cur->right, count); // 递归count += cur->right->val; // 回溯path.pop_back(); // 回溯}
使用模板3,求出的是路径的长度最大值
整个二叉树路径最长 = 左子树最长+右子树最长,所以使用递归的思想。
var diameterOfBinaryTree = function(root) {// res统计路径let res = 0// 整个二叉树路径最长 = 左子树最长+右子树最长var maxDepth = function(root){// 递归结束条件if(!root) return 0// 单层递归逻辑// 左最长let left = maxDepth(root.left)// 右最长let right = maxDepth(root.right)// 更新全局变量resres = Math.max(left+right,res)// 返回值return Math.max(left,right)+1}maxDepth(root)return res
};
使用模板3,求出的是路径上节点的和的最大值.
var maxPathSum = function(root) {// 最大路径和,res为负无穷,因为节点值可能为负数// 全局变量res的初值设置是0还是-Infinity要看题目节点是否存在负值,如果存在就用-Infinity,否则就是0let res = -Infinity// 递归函数var dfs = function(root){// 单层递归结束条件if(!root) return 0// 左右子树的和最大值,节点值可能为负数let left = Math.max(dfs(root.left),0)let right = Math.max(dfs(root.right),0)// 更新resres = Math.max(left+right+root.val,res)// 单层逻辑返回值return Math.max(left,right)+root.val}dfs(root)return res
};