Binary Trees · 30 of 35
Print all K Sum paths in a Binary tree
Find every straight downward chain of nodes in a tree whose numbers add up to k, using a notebook of running totals.
- Simple way
- time O(n * h)
room O(h) - Clever way
- time O(n)
room O(h)
The problem, simply
Every node of a binary tree holds a number, and numbers can be negative. A path here means a chain of nodes going straight down: it can start at any node and stop at any node below it, but it never goes up or sideways.
Print every path whose numbers add up to k.
A story to remember it
Picture it
k = 5. Four downward paths add up to 5: 3 then 2, 1 then 3 then 1, 1 then -1 then 5, and the single node 5.
- answer
- path
Examples
Small tree, k = 5
- In
tree = [1, 3, -1, 2, 1, 4, 5], k = 5- Out
[3, 2], [1, 3, 1], [1, -1, 5], [5]
A path can start at the root (1, 3, 1), in the middle (3, 2), or be one node (5). The minus one in 1, -1, 5 is why you must not stop a path early.
A bigger tree
- In
tree = [1, 3, -1, 2, 1, 4, 5, null, null, 1, null, 1, 2, null, 6], k = 5- Out
8 paths
They are 3 2, 1 3 1, 3 1 1, 4 1, 1 -1 4 1, -1 4 2, 5, and 1 -1 5.
All zeros
- In
tree = [0, 0, 0], k = 0- Out
5 paths
Each single node, plus root and left child, plus root and right child. Many paths can end at the same node.
Nothing fits
- In
tree = [2, 1, 3], k = 10- Out
no paths
The biggest downward path is 2 then 3, which is only 5.
Way 1: the simple way
Try every starting node. From each one, walk down every way, adding numbers as you go, and print every time the sum equals k. Do not stop when you hit k, because a minus number further down can bring you back to k again.
- For each node
sin the tree, start a walk atswith sum 0. - In the walk, add the node's value. If the sum is
k, save the path so far. - Keep walking into both children.
Starting at 3: the walk tries 3 (sum 3), 3 then 2 (sum 5, found), 3 then 1 (sum 4). Then it starts again from every other node.
- looking now
- seen
- answer
- path
import java.util.*;
class Solution {
private List<List<Integer>> found = new ArrayList<>();
public List<List<Integer>> kSumPaths(TreeNode root, int k) {
found.clear();
startEverywhere(root, k);
return found;
}
private void startEverywhere(TreeNode node, int k) {
if (node == null) return;
walkDown(node, 0, k, new ArrayList<>()); // all paths that start here
startEverywhere(node.left, k);
startEverywhere(node.right, k);
}
private void walkDown(TreeNode node, int sum, int k, List<Integer> path) {
if (node == null) return;
sum += node.val;
path.add(node.val);
if (sum == k) found.add(new ArrayList<>(path)); // do not stop, keep going
walkDown(node.left, sum, k, path);
walkDown(node.right, sum, k, path);
path.remove(path.size() - 1);
}
}How long it takes: each node is walked over once for every ancestor that starts a walk, so O(n * h). That is O(n log n) for a bushy tree and O(n^2) for a stick. Copying each answer costs extra on top. Extra room: the path and the call stack, O(h).
Way 2: the clever way
Use the notebook from the hike. Keep the running total from the root, and a map from "total" to the spots on the current path where that total happened. Spot 0 means "before the root", with total 0.
- Step onto a node. The new total is
total = old total + value. - Ask the map for
total - k. For every spotjit lists, the nodes after spotjup to here add up to exactlyk. Save them. - Write
totalinto the map at the current spot. - Go into the left child and the right child.
- On the way back up, erase
totalfrom the map, so other forks do not see it.
Ask the notebook for total minus k
01 / 09
k is 5. Before we step anywhere, the running total is 0. Write 0 in the notebook at spot 0.
- k
- 5
- found
- 0
- looking now
- seen
- answer
- path
- just added
import java.util.*;
class Solution {
private List<List<Integer>> found = new ArrayList<>();
private List<Integer> path = new ArrayList<>(); // values from the root down
private Map<Integer, List<Integer>> seen = new HashMap<>(); // total -> spots where it happened
public List<List<Integer>> kSumPaths(TreeNode root, int k) {
found.clear();
path.clear();
seen.clear();
seen.computeIfAbsent(0, x -> new ArrayList<>()).add(0); // before the root, total 0
go(root, 0, k);
return found;
}
private void go(TreeNode node, int total, int k) {
if (node == null) return;
path.add(node.val);
total += node.val;
for (int spot : seen.getOrDefault(total - k, Collections.emptyList())) {
found.add(new ArrayList<>(path.subList(spot, path.size()))); // the nodes after that spot
}
seen.computeIfAbsent(total, x -> new ArrayList<>()).add(path.size());
go(node.left, total, k);
go(node.right, total, k);
List<Integer> spots = seen.get(total);
spots.remove(spots.size() - 1); // walking back up: erase this total
path.remove(path.size() - 1);
}
}How long it takes: one visit per node and one map question per node, so O(n) to find all the paths, plus the time to copy out each answer. Extra room: the map and the path only hold the current way down, O(h).
Watch out for
- Ask the map before you write the current total into it. Otherwise, with
k = 0, every node would wrongly match an empty path ending at itself. - Forgetting to erase on the way back up lets totals from the left fork leak into the right fork and invents paths that are not straight.
- Start the map with total 0 at spot 0, or paths that start at the root are missed.
What interviewers ask next
- Only count the paths (LeetCode "path sum III"). Keep a map from total to how many times it happened instead of the spots:
O(n)time. - The textbook way. Keep the current path and, at each node, add values backwards from the node to the root, printing whenever the sum is
k. It is one walk, but stillO(n * h), like Way 1. - Paths may go up and then down (any two nodes). That is "maximum path sum" style: each node combines the best from its left and right. For counting all such paths, it gets much harder.
- Root to leaf paths only. Then a simple walk with a running sum works, and you only check at leaves.
Remember this
Grown-up words: the running totals are prefix sums, the notebook is a hash map, erasing on the way back is backtracking, and the same trick on an array is "subarray sum equals k".