Topic
- Tree
Description
https://leetcode.com/problems/path-sum-iii/
Given the root
of a binary tree and an integer targetSum
, return the number of paths where the sum of the values along the path equals targetSum
.
The path does not need to start or end at the root or a leaf, but it must go downwards (i.e., traveling only from parent nodes to child nodes).
Example 1:
Input: root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
Output: 3
Explanation: The paths that sum to 8 are shown.
Example 2:
Input: root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
Output: 3
Constraints:
- The number of nodes in the tree is in the range
[0, 1000]
. - − 1 0 9 < = N o d e . v a l < = 1 0 9 -10^9 <= Node.val <= 10^9 −109<=Node.val<=109
- − 1000 < = t a r g e t S u m < = 1000 -1000 <= targetSum <= 1000 −1000<=targetSum<=1000
Analysis
方法一:我写的,暴力算法 + DFS。
方法二:别人写的,暴力算法 + DFS,比方法一精简。
方法三:别人写的,利用Map缓存 + DFS,空间换时间。
It’s not very hard if we understand the elementary idea:
Given we have a route:
--------------sum(a,b)-------sum(b,c)
a-------------b--------------c
we know that,
sum(a,c) = sum(a,b) + sum(b,c)
iftarget == sum(b,c)
, thensum(a,c) = sum(a,b) + target
Now, we could infer that,
if exists a pointb
, that conformingsum(a,b) = sum(a,c) - target
,
the,b->c
is the path we want to find. link
Hope my comment below could help some folks better understand this solution.
The prefix stores the sum from the root to the current node in the recursion
The map stores <prefix sum, frequency> pairs before getting to the current node. We can imagine a path from the root to the current node. The sum from any node in the middle of the path to the current node = the difference between the sum from the root to the current node and the prefix sum of the node in the middle.
We are looking for some consecutive nodes that sum up to the given target value, which means the difference discussed in 2. should equal to the target value. In addition, we need to know how many differences are equal to the target value. (More detail , see below quote)
Here comes the map. The map stores the frequency of all possible sum in the path to the current node. If the difference between the current sum and the target value exists in the map, there must exist a node in the middle of the path, such that from this node to the current node, the sum is equal to the target value.
Note that there might be multiple nodes in the middle that satisfy what is discussed in 4. The frequency in the map is used to help with this.
Therefore, in each recursion, the map stores all information we need to calculate the number of ranges that sum up to target. Note that each range starts from a middle node, ended by the current node.
To get the total number of path count, we add up the number of valid paths ended by EACH node in the tree.
Each recursion returns the total count of valid paths in the subtree rooted at the current node. And this sum can be divided into three parts:
- the total number of valid paths in the subtree rooted at the current node’s left child
- the total number of valid paths in the subtree rooted at the current node’s right child
- the number of valid paths ended by the current node
The interesting part of this solution is that the prefix is counted from the top(root) to the bottom(leaves), and the result of total count is calculated from the bottom to the top 😄 link
另外,这单一方法里,将二叉树的前序遍历,后序遍历两模式混合在一起使用,有点回溯算法的味道。
方法四:方法三的另一种写法。
Submission
import java.util.HashMap;
import com.lun.util.BinaryTree.TreeNode;
public class PathSumIII {
//方法一:我写的,暴力算法 + DFS
public int pathSum(TreeNode root, int targetSum) {
int[] findCount = {0};
dfs(root, findCount, targetSum);
return findCount[0];
}
private void dfs(TreeNode node, int[] findCount, int targetSum) {
if(node == null) return;
dfs2(node, 0, findCount, targetSum);
dfs(node.left, findCount, targetSum);
dfs(node.right, findCount, targetSum);
}
private void dfs2(TreeNode node, int sum, int[] findCount, int targetSum) {
if(node == null) return;
if((sum += node.val) == targetSum)
findCount[0]++;
dfs2(node.left, sum, findCount, targetSum);
dfs2(node.right, sum, findCount, targetSum);
}
//方法二:别人写的,暴力算法 + DFS
public int pathSum2(TreeNode root, int sum) {
if (root == null) return 0;
return pathSumFrom(root, sum) + pathSum2(root.left, sum) + pathSum2(root.right, sum);
}
private int pathSumFrom(TreeNode node, int sum) {
if (node == null) return 0;
return (node.val == sum ? 1 : 0)
+ pathSumFrom(node.left, sum - node.val) + pathSumFrom(node.right, sum - node.val);
}
//方法三:别人写的,利用缓存,空间换时间
public int pathSum3(TreeNode root, int sum) {
HashMap<Integer, Integer> preSumMap = new HashMap<>();
preSumMap.put(0,1);
int[] count = {0};
helper(root, 0, sum, preSumMap, count);
return count[0];
}
public void helper(TreeNode root, int currSum, int target, HashMap<Integer, Integer> preSumMap, int[] count) {
if (root == null)
return;
currSum += root.val;
if (preSumMap.containsKey(currSum - target)) {
count[0] += preSumMap.get(currSum - target);
}
if (!preSumMap.containsKey(currSum)) {
preSumMap.put(currSum, 1);
} else {
preSumMap.put(currSum, preSumMap.get(currSum) + 1);
}
//上面语块可换写成一句map.put(sum, map.getOrDefault(sum, 0) + 1);
helper(root.left, currSum, target, preSumMap, count);
helper(root.right, currSum, target, preSumMap, count);
preSumMap.put(currSum, preSumMap.get(currSum) - 1);
}
//方法四:方法三的另一种写法
public int pathSum4(TreeNode root, int sum) {
if (root == null) return 0;
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
return findPathSum(root, 0, sum, map);
}
private int findPathSum(TreeNode curr, int sum, int target, HashMap<Integer, Integer> map) {
if (curr == null) {
return 0;
}
// update the prefix sum by adding the current val
sum += curr.val;
// get the number of valid path, ended by the current node
int numPathToCurr = map.getOrDefault(sum - target, 0);
// update the map with the current sum, so the map is good to be passed to the next recursion
map.put(sum, map.getOrDefault(sum, 0) + 1);
// add the 3 parts discussed in 8. together
int res = numPathToCurr + findPathSum(curr.left, sum, target, map)
+ findPathSum(curr.right, sum, target, map);
// restore the map, as the recursion goes from the bottom to the top
map.put(sum, map.get(sum) - 1);
return res;
}
}
Test
import static org.junit.Assert.*;
import org.junit.Test;
import com.lun.util.BinaryTree;
import com.lun.util.BinaryTree.TreeNode;
public class PathSumIIITest {
@Test
public void test() {
PathSumIII obj = new PathSumIII();
TreeNode root1 = BinaryTree.integers2BinaryTree(10, 5, -3, 3, 2, null, 11, 3, -2, null, 1);
assertEquals(3, obj.pathSum(root1, 8));
assertEquals(3, obj.pathSum2(root1, 8));
assertEquals(3, obj.pathSum3(root1, 8));
assertEquals(3, obj.pathSum4(root1, 8));
TreeNode root2 = BinaryTree.integers2BinaryTree(5, 4, 8, 11, null, 13, 4, 7, 2, null, null, 5, 1);
assertEquals(3, obj.pathSum(root2, 22));
assertEquals(3, obj.pathSum2(root2, 22));
assertEquals(3, obj.pathSum3(root2, 22));
assertEquals(3, obj.pathSum4(root2, 22));
}
}