在算法面试和编程竞赛中,二叉树路径求和是一类经典问题。题目要求我们判断是否存在从根节点到叶子节点的路径,使得路径上所有节点值之和等于给定的目标值。这类问题不仅考察对二叉树结构的理解,也检验递归算法的应用能力。
给定一个二叉树的根节点和一个整数sum,我们需要确定是否存在从根节点到叶子节点的路径,使得路径上的节点值之和等于sum。例如:
code复制 5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
对于sum=22,存在路径5→4→11→2,其和为5+4+11+2=22,因此返回true。
解决这个问题的关键在于:
递归是最自然的实现方式,因为二叉树的定义本身就是递归的。每次递归调用时,我们将问题分解为更小的子问题:检查左子树和右子树是否存在满足条件的路径。
java复制public boolean hasPathSum(TreeNode root, int sum) {
if(root == null) {
return false;
}
if(root.left == null && root.right == null && sum - root.val == 0) {
return true;
}
return hasPathSum(root.left, sum - root.val) ||
hasPathSum(root.right, sum - root.val);
}
递归终止条件有两个:
注意:判断叶子节点时要同时检查左右子节点都为null,不能只检查一个方向。
在递归调用时,我们采用"sum - root.val"的方式传递剩余的目标值,这种技巧有几个优点:
最坏情况下需要访问所有节点,时间复杂度为O(n),其中n是节点数量。这是最优的时间复杂度,因为任何算法至少需要检查每个节点一次。
递归调用栈的深度等于树的高度:
对于大型二叉树,递归可能导致栈溢出。这时可以考虑使用迭代法(借助栈实现DFS)来避免递归带来的栈溢出风险。
java复制public boolean hasPathSumIterative(TreeNode root, int sum) {
if(root == null) return false;
Stack<TreeNode> nodeStack = new Stack<>();
Stack<Integer> sumStack = new Stack<>();
nodeStack.push(root);
sumStack.push(sum - root.val);
while(!nodeStack.isEmpty()) {
TreeNode node = nodeStack.pop();
int currSum = sumStack.pop();
if(node.left == null && node.right == null && currSum == 0) {
return true;
}
if(node.right != null) {
nodeStack.push(node.right);
sumStack.push(currSum - node.right.val);
}
if(node.left != null) {
nodeStack.push(node.left);
sumStack.push(currSum - node.left.val);
}
}
return false;
}
实际编码中容易忽略的边界情况:
当递归算法出现问题时,可以:
如果需要返回所有路径而不仅仅是判断是否存在,可以修改算法:
java复制public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<List<Integer>> result = new ArrayList<>();
dfs(root, sum, new ArrayList<>(), result);
return result;
}
private void dfs(TreeNode node, int sum, List<Integer> path, List<List<Integer>> result) {
if(node == null) return;
path.add(node.val);
if(node.left == null && node.right == null && node.val == sum) {
result.add(new ArrayList<>(path));
} else {
dfs(node.left, sum - node.val, path, result);
dfs(node.right, sum - node.val, path, result);
}
path.remove(path.size() - 1);
}
如果路径不需要从根节点开始,问题会变得更加复杂,通常需要双重递归:
java复制public int pathSumFromAny(TreeNode root, int sum) {
if(root == null) return 0;
return helper(root, sum) +
pathSumFromAny(root.left, sum) +
pathSumFromAny(root.right, sum);
}
private int helper(TreeNode node, int sum) {
if(node == null) return 0;
return (node.val == sum ? 1 : 0) +
helper(node.left, sum - node.val) +
helper(node.right, sum - node.val);
}
在实际面试中,理解递归解决二叉树路径求和问题的核心思想后,可以举一反三解决各种变种问题。关键在于清晰地定义递归的终止条件和正确地传递状态参数。