前缀和的概念
1、所谓前缀和,就是求从根节点到当前节点所经过的所有节点的和。设根节点的为root,当前节点为node5,从root到node5依次进过了node2,node3,node4,则node5的前缀和为root+node2+node3+node4+node5。其他的节点以此类推。
2、当求是否存在路径总和为target时,以target为8举例子,假设node2的前缀和为2,node5的前缀和为10,则可以断定node3+node4+node5的路径总和为8。
深度优先搜索:先序遍历
1、因为前缀和是从根节点到当前节点所经过的所有节点的和,即我们要遍历同一个路径的上的节点,因此应使用深度优先搜索来遍历节点。
2、又因为前缀和是从根节点到当前节点所经过的所有节点的和,要求每次要加上本节点的和,因此要先拿到本节点的值,再遍历本节点的子节点。这就是所谓前序遍历,又叫先序遍历,即根在前,然后左右子节点。
综上,我们在求某个节点的前缀和时,应使用前序遍历,将这个节点之前的所有节点的值加起来,成为这个节点的前缀和。
哈希表:记录前缀和、降低复杂度 O(1)
1、接下来,继续以是否存在路径总和为target时,以target为8举例子,假设node2的前缀和为2,node5的前缀和为10,则可以断定node3+node4+node5的路径总和为8。当我们求导node5的前缀和时,我们必须要知道它之前的节点有没有一个前缀和为2的节点。这就需要有一个备忘录,显然字典是备忘录的首选。因此,我们要定义一个字典,用来存储每个节点及它之前的节点的前缀和。
2、我们只关心有没有符合要求的前缀和就行了。即当node5的前缀和是10时,我们只需要知道在node5之前的节点里,有没有前缀和为2的节点就行了,至于这个前缀和为2的节点距离node5有多远,经过了哪几个节点,不关心。因此字典里,只需要呈现前缀和为2的节点,有还是没有。那么,我们是否要求知道前缀和为2的节点有几个呢?我理解是需要的。因为题目里并没有说节点的值都是正值,因此节点里可能有负值。假设node2的前缀和为2,node3的值(非前缀和)为3,node4的值(非前缀和)为-3,那么node4的前缀和也为2.此时,若node5的前缀和为10,则如果字典里显示前缀和为2的节点有2个,则我们可以知道截止到node5,存在2个路径之和为8.
因此,该字典的键为出现的前缀和,值为这个前缀和出现的次数。
节点的前缀和只与其子树有关,子树遍历之后需要删除该节点的前缀和
继续以以当求是否存在路径总和为target时,以target为8举例子,假设node2的前缀和为2,node5的前缀和为10,则可以断定node3+node4+node5的路径总和为8为例子。假设有一个node10的前缀和也是10,但是node10压根不和node2~4在一个路径上,那node10是不能用node2的前缀和的。那么,该如何避免这种情况呢?那就是当node2的左右两个子树均搜索完毕后,应在字典里,将前缀和为2的数量减去1。为什么呢?因为node2的前缀和只能为它的子树所用,其他的树节点不能用node2的前缀和。因此在搜索完node2的左右子树后,要在字典里把node2的前缀和对应的值减去1.
class Solution:
def pathSum(self, root: TreeNode, targetSum: int) -> int:
prefix = collections.defaultdict(int)
# 这里的prefix[0] = 1是为了计算某些单个节点就直接为target的情况
prefix[0] = 1
def dfs(root, curr):
if not root:
return 0
ret = 0
curr += root.val
# 判断字典中有没有符合条件的前缀和
ret += prefix[curr - targetSum]
# 有相同则累加,没有则赋值为1
prefix[curr] += 1
ret += dfs(root.left, curr)
ret += dfs(root.right, curr)
# 当前root遍历后回溯需要-1,保证该节点的前缀和只能用于该节点的子树
prefix[curr] -= 1
return ret
return dfs(root, 0)