Home > Reflections | ⏮️ ⏭️
2024-08-01
🏋 Coding Practice
112. Path Sum
Given the
root
of a binary tree and an integertargetSum
, returntrue
if the tree has a root-to-leaf path such that adding up all the values along the path equalstargetSum
.
A leaf is a node with no children.
🪞 Reflections
- Never great to get a wrong submission
- This was due to a misunderstanding of the problem.
- A careless mistake - I should read the description more carefully.
- My initial implementation did what I wanted it to - just not what the problem was looking for.
- This highlights the importance of asking clarifying questions during a real interview
- Finding my way back to a working solution in under 25 minutes isn’t terrible
- The initial implementation came out quickly and without much waffling or difficulty
- Manual testing
- I added the null-checks as I wrote the initial code, which feels more efficient than coming back afterword
- while my tree notation for test values isn’t hyper-efficient, it’s much easier to work with than the super efficient, but easy to misread list annotation used in LeetCode examples (e.g.
[1,2,3,null]
- Testing the recursive implementation felt natural and effective
- All in all, a nice warm up for my real interview later today
- I intentionally chose an
Easy
LeetCode problem just to warm up my usual routine without inducing too much stress by struggling with a more challenging problem on the day of an interview.
- I intentionally chose an
⌨️ My Solution
/* Determine if a root-to-leaf path summing to target exists
Thoughts
1. In the worst case, we have to traverse the entire tree
2. But there's a lot of overlap in the paths
3. Depth first search seems appropriate so we can stop as soon as we get a match
4. Trees are recursive data structures - a recursive algorithm feels appropriate
Plan:
1. Implement a recursive depth first search
2. maintain a path-prefix sum until reaching each leaf
3. return the OR (lazily if possible) of the boolean response of each child
4. micro-optimization: if there are no negative values, we could abandon a sub-tree when we surpass the target
- note: negative values are possible, so we can't abandon early
[4:20] Done planning
[9:16] Done with initial implementation
[15:04] Done with test
[16:05] Initial Submission
Bug found after submission
1. Wrong Answer -> [], 0 (expected false, yielded true)
- Okay, I guess the empty sum in this case isn't zero; should have been a clarifying question
[18:30] fixed above
[18:45] Next submission
Bug found after submission:
1. Wrong Answer -> [1,2], 1 (expected false, yielded true)
- Ah - language from the description: "A leaf is a node with no children"
- so don't follow paths to the null
[24:40] Successful submission
Oh yeah, this is linear (O(N)) in runtime and extra space complexity
*/
/**
* Definition for a binary tree node.
* class TreeNode {
* val: number
* left: TreeNode | null
* right: TreeNode | null
* constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
* }
*/
function hasPathSum(root: TreeNode | null, targetSum: number, prefixSum: number | null = null): boolean { // null,4,4; {v:3},4,1; null, 4, 10; {v:9}, 4, 1; root={v:1,l:{v:9},r:{v:3}} targetSum=4
if (!root) return false
const newPrefixSum: number = (prefixSum || 0) + root.val // 4; 10; 1; null-check: safe access
if (!root.left && !root.right) {
return newPrefixSum === targetSum // 4 === 4 => true; 10 !== 4 => false
}
return (root.left ? hasPathSum(
root.left, // null; null; {v:9}; null-check: safe access
targetSum, // 4; 4; 4
newPrefixSum // 4; 10; 1
) : false) || (root.right ? hasPathSum( // lazy OR for early-returning depth-first search
root.right, // {v:3}; null; null-check: safe access
targetSum, // 4; 4
newPrefixSum // 1; 10
) : false)
};