《程序员面试金典(第6版)》面试题 04.12. 求和路径
创始人
2025-05-30 08:09:31

题目描述

给定一棵二叉树,其中每个节点都含有一个整数数值(该值或正或负)。设计一个算法,打印节点数值总和等于某个给定值的所有路径的数量。注意,路径不一定非得从二叉树的根节点或叶节点开始或结束,但是其方向必须向下(只能从父节点指向子节点方向)。

示例:
给定如下二叉树,以及目标和 sum = 22,

          5/ \4   8/   / \11  13  4/  \    / \7    2  5   1

返回: 3
解释:和为 22 的路径有:[5,4,11,2], [5,8,4,5], [4,11,7]

提示:节点总数 <= 10000

解题思路与代码

这道题,在我看来,是在考验你能不能从题目描述中,抽象出来它到底想表达什么意思。如果你做不到这一点,那对你来说就是hard难度,如果你能抽象出来它的意思,那就还不错,可能会觉得稍微有点简单。

下面就让我来带大家来领略这道题真正想要表达的意思:

思考这样一个问题,这道题,是让你去求题目所给的二叉树所有的路径和,然后简简单单的对比一下和sum相当吗?
如果你这么想,那就说明你想的还是太简单了。你还在第一层,而出题人在第二层。

这道题真正的题意,返回当前二叉树中所有节点都作为一颗二叉树,符合等于sum的路径和的数量。

了解了抽象出来的题意后,我们再来根据题意撰写我们的代码。

深度优先搜索(DFS)(前序遍历)

我们刚刚已经了解了真正的题意了。也就不难写出符合题意的优质代码。

如果说,返回当前二叉树中所有节点都作为一颗二叉树,符合等于sum的路径和的数量,这是一个大问题的话,那么返回单个节点作为一颗二叉树,返回所有等于sum的路径和,这就是一个小问题。

我们只有先将小问题的代码逻辑写好了,才能再去写大问题的代码逻辑。这是自底向上的,没有根基,也就没有高楼。

很巧的是这个小问题,其实也就是我们刚刚想的第一层,它本质上,也是一个递归函数。我们还能再去抽象出它的单层逻辑来,然后去撰写它的递归代码。下面是它的单层逻辑:

  • 首先先设置一个值ret置为0。先判断 单个节点的值是否与sum相等。如果相等,则这就是一个符合sum的路径和。

  • 然后因为判断完这单个节点了,接下来就该判断以这个节点作为一个二叉树的根节点,有没有符合sum的路径和。

现在对于单个节点的单层递归逻辑,到现在为止,我们就已经撰写完毕了。不难发现,这个递归函数的返回值是int类型的。然后需要TreeNode*与int作为形参传递进来。
那递归结束的条件呢?自然是如果root为空节点,然后返回0啦~。

下面给出单个节点作为一颗二叉树,返回所有等于sum的路径和的代码:

int oneNodeOfTree(TreeNode* root,int sum){if(root == nullptr) return 0;int ret = 0;if(root->val == sum) ++ret;ret += oneNodeOfTree(root->left,sum - root->val);ret += oneNodeOfTree(root->right,sum - root->val);return ret;}

现在我们再重申一下这道题抽象出来的题目要求:返回当前二叉树中所有节点都作为一颗二叉树,符合等于sum的路径和的数量

我们已经做完单个节点作为一颗二叉树,返回所有等于sum的路径和的代码了,现在就是想想这个二叉树有多少个节点嘛,然后我们把这些节点所有符合路径和的数量加起来就好了嘛。

这其实也是个递归函数。甚至可以说是前序遍历,因为我们要去统计二叉树中每一个节点。

这个函数逻辑也就更好写,其实和上面的逻辑差不多。唯一的不同就是 ret 的值是不同的。

单个节点的递归函数ret的初始值是0,这是因为我们只需要判断以这个节点为二叉树,他有多少条符合sum的路径和。
而整个二叉树所有节点的ret的初始值为 oneNodeOfTree(root,sum),这是因为,整个二叉树头节点也是一颗二叉树,它也有很多条路径,我们需要先把他作为单独的一个节点,扔进单个节点的递归函数,去作为ret的初始值。

然后之后的逻辑也差不多,具体的看代码就好了,我也就不多赘述了:

int pathSum(TreeNode* root, int sum) {if(root == nullptr) return 0;int ret = oneNodeOfTree(root,sum);ret += pathSum(root->left,sum);ret += pathSum(root->right,sum);return ret;}

下面是整体的代码:

class Solution {
public:int oneNodeOfTree(TreeNode* root,int sum){if(root == nullptr) return 0;int ret = 0;if(root->val == sum) ++ret;ret += oneNodeOfTree(root->left,sum - root->val);ret += oneNodeOfTree(root->right,sum - root->val);return ret;}int pathSum(TreeNode* root, int sum) {if(root == nullptr) return 0;int ret = oneNodeOfTree(root,sum);ret += pathSum(root->left,sum);ret += pathSum(root->right,sum);return ret;}};

在这里插入图片描述

复杂度分析

时间复杂度:

  • 小问题的递归函数的时间复杂度为O(n)O(n)O(n),n为二叉树节点的数量。大问题呢,是要遍历整颗二叉树,也要遍历每个节点,每一个节点作为一个小问题,那就是n个小问题。所以总体的时间复杂度就是O(n2)O(n^2)O(n2)

空间复杂度:

  • 关于空间复杂度,由于使用了递归,在递归过程中调用栈将保存在内存中,所以空间复杂度也是O(n)O(n)O(n)。
    其中nnn表示树中节点的数量。这是由于在最坏情况下,当树退化为链表时,代码递归到最深处,将会有nnn层的函数调用堆栈被记录下来。

优化:前缀表

关于什么是前缀表可以去看我的这篇文章什么是前缀表,下面来说说,前缀表,到底做了哪些优化。

该解决方案采用哈希表原理来存储数据,并利用前缀和的思想来解决子问题。在访问每个节点时,我们都检查是否有累加到期望和的路径存在。因此时间复杂度为:O(N),其中N是树中节点的数量。哈希表操作的空间要求是O(N),因为在最坏情况下哈希表将保存所有节点。同时, 这种做法的空间复杂度等价于我们使用队列迭代遍历的空间复杂度。所以空间复杂度也是O(N)。

综合而言,比较代码段1与代码段2,发现第二段代码是更好解决方案,因为它具有更低的时间复杂度和更高的空间效率,同时其递归深度更小,这使得其通常不会耗尽程序堆栈。

下面给出具体的代码实现:

class Solution {
public:unordered_map prefix; //前缀表int pathSum(TreeNode* root, int sum) {if(root == nullptr) return 0;prefix[0] = 1;//这么设置是因为,当表中第一次出现符合路径的时候,cur - sum的值会是0,所以我们的ret就会加上这个1,所以这里设置的键值对就为(0,1)int cur = 0; //这个cur是路径到当前节点的累加值。return dps(root,cur,sum);}int dps (TreeNode* root,int cur,int sum){if(root == nullptr) return 0;int ret = 0; //这个ret是最终返回的符合对应路径值的个数cur += root->val;if(prefix.count(cur - sum)) ret = prefix[cur-sum];//如果这个我们在前序表中,找到了这个键了,那么ret就加上对应的值。注意,键值可不只可能是0!!prefix[cur]++; //将当前路径的累加值作为一个键添加到前序表中,并将键对应的值加1。ret += dps(root->left,cur,sum); //遍历左子树,将左子树可能的路径都加入前序表中。ret += dps(root->right,cur,sum);//遍历右子树,将右子树可能的路径也都加入前序表中//当我们遍历完一条路径的时候,我们要将对应键值对中的值清空。如果不清空,前序遍历到右子树的时候,//就有可能出现找到了之前左子树没有清空的那个键,导致 多加了一个错误的1prefix[cur]--;return ret;}
};

总结

这道题一点也不算简单。真的是很考验,将题目抽象出具体的意思的能力。还有就是考虑你对一些算法技巧或思想的掌握情况,比如这道题就考验到了你关于算法前缀和这个思想理解的透不透彻,能不能想到用这种方式去解决问题。

相关内容

热门资讯

贾国龙回应罗永浩?最新消息:两... 截至1月16日22时50分,贾国龙宣称的“我们将就罗永浩对西贝的重大污蔑诽谤一一全面回应”并没有到来...
动态海报丨绿满山川,金满坡!数...     编者按:2025年我国林草产业总产值近11万亿元,进出口贸易总额超1800亿美元,带动超60...
计息落地+智能合约,数字人民币... 本报(chinatimes.net.cn)记者付乐 北京报道 近日,《关于进一步加强数字人民币管理服...
向日葵被立案调查,重组终止,股... 监管层对“忽悠式重组”保持“零容忍”高压态势。1月14日,因公司涉嫌信息披露违法,向日葵公司(300...
山西太原“首店经济”迎新 激活... 16日,山西太原小店区茂业天地购物中心内,盒马鲜生山西首店开门营业,吸引众多市民“尝鲜”。盒马鲜生供...