leetcode 437路径总和,前缀和知识总结

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。

路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
在这里插入图片描述

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/path-sum-iii

分析:这是一道典型的考前缀和知识的题。
以我的理解,所谓前缀和,就是求从根节点到当前节点所经过的所有节点的和。设根节点的为root,当前节点为node5,从root到node5依次进过了node2,node3,node4,则node5的前缀和为root+node2+node3+node4+node5。其他的节点以此类推。
当求是否存在路径总和为target时,以target为8举例子,假设node2的前缀和为2,node5的前缀和为10,则可以断定node3+node4+node5的路径总和为8.
因此,我们在每走到一个节点时,计算它的前缀和,对求路径总和很有意义。
又因为前缀和是从根节点到当前节点所经过的所有节点的和,即我们要遍历同一个路径的上的节点,因此应使用深度优先搜索来遍历节点。
又因为前缀和是从根节点到当前节点所经过的所有节点的和,要求每次要加上本节点的和,因此要先拿到本节点的值,再遍历本节点的子节点。这就是所谓前序遍历,又叫先序遍历,即根在前,然后左右子节点。
综上,我们在求某个节点的前缀和时,应使用前序遍历,将这个节点之前的所有节点的值加起来,成为这个节点的前缀和。
------------------------------------------
接下来,继续以当求是否存在路径总和为target时,以target为8举例子,假设node2的前缀和为2,node5的前缀和为10,则可以断定node3+node4+node5的路径总和为8。当我们求导node5的前缀和时,我们必须要知道它之前的节点有没有一个前缀和为2的节点。这就需要有一个备忘录,显然字典是备忘录的首选。因此,我们要定义一个字典,用来存储每个节点以及它之前的节点的前缀和。
那么,字典如何设计?它的键值分别是什么?让我们再回到题目,只求和为8的路径的个数,而不是求和为8的路径。因此,我们只关心有没有符合要求的前缀和就行了。即当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.

整体代码如下,下面代码里insert是添加节点的函数,find_sum_path是本文所讲的求路径之和的函数:

class Node:
    def __init__(self,value):
        self.value = value
        self.left_child = None
        self.right_child = None
    def insert(self,value):
        queue = [self]
        while len(queue) != 0:
            current = queue.pop(0)
            if current.left_child is None:
                current.left_child = Node(value)
                return
            else:
                queue.append(current.left_child)
            if current.right_child is None:
                current.right_child = Node(value)
                return
            else:
                queue.append(current.right_child)
        def find_sum_path(self,target):
            sum_dict = collections.defaultdict(int)
            # 这里的persum[0] = 1是为了计算某些单个节点就直接为target的情况
            sum_dict[0] = 1
            def dfs(current,current_sum):
                if current is None:
                    return 0
                current_sum += current.val            
                result = sum_dict[current_sum-targetSum]
                # 注意result一定要先算,然后才能将当前的前缀和放入字典,不能反了。
                # 因为sum_dict指示的是当前节点之前的前缀和和有哪些,如果先把包含自己的前缀和放入字典,
                # 就会出现自己算自己的情况。比如设tree=[1],target=0,如果先把sum_dict[1]=1放入字典,
                # 然后再算result = sum_dict[1-0],就会得出result=1.
                sum_dict[current_sum] += 1
                if current.left:
                    result += dfs(current.left,current_sum)
                if current.right:
                    result += dfs(current.right,current_sum)
                sum_dict[current_sum] -= 1
                return result
        return dfs(root,0)

root = Node(5)
root.insert(2)
root.insert(4)
root.insert(1)
root.insert(6)
root.insert(3)
root.insert(7)
print(root.find_sum_path(7))
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值