Path Sum
EasyPrompt
Given the root of a binary tree and an integer targetSum, return true if the tree has a root-to-leaf path such that adding up all the values along the path equals targetSum.
A leaf is a node with no children.
Example 1:
Input: root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
Output: true
Explanation: The root-to-leaf path with the target sum is shown.Example 2:
Input: root = [1,2,3], targetSum = 5
Output: false
Explanation: There are two root-to-leaf paths in the tree:
(1 --> 2): The sum is 3.
(1 --> 3): The sum is 4.
There is no root-to-leaf path with sum = 5.Example 3:
Input: root = [], targetSum = 0
Output: false
Explanation: Since the tree is empty, there are no root-to-leaf paths.
Constraints:
- The number of nodes in the tree is in the range
[0, 5000]. -1000 <= Node.val <= 1000-1000 <= targetSum <= 1000
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses recursion to traverse the tree in a depth-first manner. The core idea is to subtract the current node's value from the targetSum and pass this new sum to its children. The recursion continues until a leaf node is reached, at which point we check if the accumulated path sum equals the target.
Algorithm
-
- Check if the
rootisnull. If so, returnfalseas there are no paths.
- Check if the
-
- Check if the current
rootis a leaf node (root.left == null && root.right == null).
- Check if the current
-
- If it is a leaf, return
trueifroot.val == targetSum, otherwisefalse.
- If it is a leaf, return
-
- If it's not a leaf, recursively call
hasPathSumfor the left child withtargetSum - root.val.
- If it's not a leaf, recursively call
-
- Recursively call
hasPathSumfor the right child withtargetSum - root.val.
- Recursively call
-
- Return
trueif either of the recursive calls returnstrue, indicating a path was found.
- Return
Walkthrough
The algorithm defines a function that takes a tree node and the remaining sum required to reach the target.
- Base Case 1: If the current node is
null, it means we've traversed past a leaf without finding a valid path from that branch, so we returnfalse. - Path Sum Update: We update the required sum by subtracting the current node's value:
newTargetSum = currentSum - node.val. - Base Case 2 (Leaf Node): If the current node is a leaf (it has no left or right child), we check if its value equals the remaining
targetSum. If it does, it means the path from the root to this leaf adds up to the originaltargetSum, so we returntrue. - Recursive Step: If the current node is not a leaf, we recursively call the function for its left and right children with the updated
targetSum. If either of these calls returnstrue(meaning a valid path was found in either the left or right subtree), we returntrue. Otherwise, we returnfalse.
/** * 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 boolean hasPathSum(TreeNode root, int targetSum) { if (root == null) { return false; } // If it's a leaf node, check if its value equals the remaining targetSum if (root.left == null && root.right == null) { return targetSum == root.val; } // Recursively call for left and right children with updated targetSum boolean hasPathInLeft = hasPathSum(root.left, targetSum - root.val); boolean hasPathInRight = hasPathSum(root.right, targetSum - root.val); return hasPathInLeft || hasPathInRight; }}Complexity
Time
O(N)
Space
O(H)
Trade-offs
Pros
Very intuitive and easy to read.
The code is concise and directly reflects the problem's recursive structure.
Cons
For very deep trees, this can lead to a
StackOverflowErrordue to the depth of the recursion stack.
An alternative to recursion is to use an iterative approach with an explicit data structure, like a Stack for Depth-First Search (DFS) or a Queue for Breadth-First Search (BFS). This approach avoids the limitations of the recursion stack depth and can be more efficient in terms of space for certain tree structures.
Algorithm
-
- If
rootisnull, returnfalse.
- If
-
- Create a
nodeStackand asumStack.
- Create a
-
- Push
roottonodeStackandtargetSum - root.valtosumStack.
- Push
-
- Loop as long as
nodeStackis not empty:
- a. Pop
currentNodefromnodeStackandcurrentSumfromsumStack. - b. If
currentNodeis a leaf andcurrentSumis 0, returntrue. - c. If
currentNode.rightexists, push it tonodeStackand pushcurrentSum - currentNode.right.valtosumStack. - d. If
currentNode.leftexists, push it tonodeStackand pushcurrentSum - currentNode.left.valtosumStack.
- Loop as long as
-
- If the loop finishes, return
false.
- If the loop finishes, return
Walkthrough
We can simulate the recursive DFS approach using an explicit Stack. The idea is to maintain a stack of nodes to visit and another stack to store the remaining sum corresponding to each node.
- Initialization: If the root is
null, returnfalse. Otherwise, push therootnode onto anodeStackand the initial remaining sum (targetSum - root.val) onto asumStack. - Traversal Loop: While the stacks are not empty, pop a node and its corresponding sum.
- Leaf Check: If the popped node is a leaf node and its corresponding remaining sum is zero, we have found a valid path. Return
true. - Push Children: If the node is not a leaf, push its children (if they exist) onto the
nodeStack. For each child, push the updated remaining sum (currentSum - child.val) onto thesumStack. - No Path Found: If the loop completes without returning, it means no such path exists. Return
false.
A similar logic can be applied using a Queue for a BFS traversal, where nodes and sums are enqueued and dequeued instead of pushed and popped.
// Iterative DFS using a Stackimport java.util.Stack; class Solution { public boolean hasPathSum(TreeNode root, int targetSum) { if (root == null) { return false; } Stack<TreeNode> nodeStack = new Stack<>(); Stack<Integer> sumStack = new Stack<>(); nodeStack.push(root); sumStack.push(targetSum - root.val); while (!nodeStack.isEmpty()) { TreeNode currentNode = nodeStack.pop(); int currentSum = sumStack.pop(); if (currentNode.left == null && currentNode.right == null && currentSum == 0) { return true; } if (currentNode.right != null) { nodeStack.push(currentNode.right); sumStack.push(currentSum - currentNode.right.val); } if (currentNode.left != null) { nodeStack.push(currentNode.left); sumStack.push(currentSum - currentNode.left.val); } } return false; }}Complexity
Time
O(N)
Space
O(H) for DFS, O(W) for BFS
Trade-offs
Pros
Avoids
StackOverflowErrorfor very deep trees, making it more robust than the recursive solution.The space complexity can be better than the alternative depending on the tree's shape (DFS is better for wide trees, BFS for deep/skewed trees).
Cons
The code is generally more verbose and less intuitive than the recursive counterpart.
Requires managing an explicit data structure (stack or queue).
Solutions
Solution
/** * 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 boolean hasPathSum ( TreeNode root , int targetSum ) { return dfs ( root , targetSum ); } private boolean dfs ( TreeNode root , int s ) { if ( root == null ) { return false ; } s -= root . val ; if ( root . left == null && root . right == null && s == 0 ) { return true ; } return dfs ( root . left , s ) || dfs ( root . right , s ); } }/** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {TreeNode} root * @param {number} targetSum * @return {boolean} */ var hasPathSum = function (root, targetSum) { function dfs(root, s) { if (!root) return false; s += root.val; if (!root.left && !root.right && s == targetSum) return true; return dfs(root.left, s) || dfs(root.right, s); } return dfs(root, 0); };/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: bool hasPathSum ( TreeNode * root , int targetSum ) { function < bool ( TreeNode * , int ) > dfs = [ & ]( TreeNode * root , int s ) -> int { if ( ! root ) return false ; s += root -> val ; if ( ! root -> left && ! root -> right && s == targetSum ) return true ; return dfs ( root -> left , s ) || dfs ( root -> right , s ); }; return dfs ( root , 0 ); } };# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution : def hasPathSum ( self , root : Optional [ TreeNode ], targetSum : int ) -> bool : def dfs ( root , s ): if root is None : return False s += root . val if root . left is None and root . right is None and s == targetSum : return True return dfs ( root . left , s ) or dfs ( root . right , s ) return dfs ( root , 0 )Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.