Leetcode 437 -- dfs&&前缀和
题目描述
思路
由于题目的范围较小,所有点的个数小于一千,因此我们可以暴力 \(dfs\) 套$ dfs$,以每一个节点作为根节点,然后向下遍历求每一个节点到该节点的路径和,时间复杂度为 \(O(N^2)\)。
但这种做法肯定会有大量重复计算,那么有没有什么办法可以消除这些重复计算呢?
我们想一下,在暴力 \(dfs\) 套$ dfs$中,我们的每个节点都会计算出每一个下层节点到该节点的路径和,那么当我们遍历到下层节点的时候,有没有什么办法可以利用这些上层节点已经计算完的数据呢?
例如下图:
1
/
2
/
3
/
4
当我们遍历到节点 \(4\) 的时候,我们是可以得到节点 \(1\) 到节点 \(2,3,4\) 和节点 \(2\) 到节点 \(3, 4\) 以及节点 \(3\) 到节点 \(4\) 的路径之和的,问题是该怎么利用?
想要利用这些数据,我们就不能再从根节点往下找路径和,为什么?
如果我们从根节点往下找路径和,那么上层节点是什么样与我们根本就没有关系,我们只关心下层节点是什么样子的。
因此,我们需要转换思路,求每个根节点到它上层所有节点的路径之和,这样,我们就可以利用这些上层节点已经计算完的数据了。
问题来了,该怎么利用?
在思考这个问题之前,你应该发现,这些数据其实就是以每一个节点作为根节点求得的到其下层节点的前缀和。
因此我们只需要将这些信息用一个数据结构保存起来即可!
现在,走到节点 \(4\) 的时候,路径和为 \(1+2+3+4=10\),此时如果存在路径和等于 \(targetSum-10\),那么就存在节点 \(4\) 到上层某个节点的一条符合要求的路径和。
并且这条路径肯定是和节点 \(4\) 联通的,因为我们去掉的是由根节点 \(1\) 到某个节点 \(x\) 的路径,得到的是节点 \(x\) 到节点 \(4\) 的路径。
所以说,我们前缀和的定义为:根节点到每个下层节点的前缀和。
注意这个根节点是最顶层的根节点,而不是将每一个节点都视为根节点,因为我们只需要知道最顶层的根节点到每一个节点的前缀和就可以找到某一个节点到任意上层节点的前缀和,通过减法。
另外,我们还需要记录某一个前缀和的个数,因为节点的值可能为 \(0\)。
另外一些细节见代码。
代码--前缀和
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
unordered_map<long long, int> prefix; // <前缀和, 个数>
int dfs(TreeNode *root, long long curSum, int targetSum)
{
if(root == NULL) return 0;
int ans = 0;
curSum += root->val;
if(prefix.count(curSum - targetSum)) // 不存在时不会创建对象
ans = prefix[curSum - targetSum]; // 上层是否存在前缀和[curSum-trgetSum]
// 题目规定,路径和之能往下走,所以说我们需要回溯 prefix 的值,否则就会出现往上走的情况
prefix[curSum] ++ ;
ans += dfs(root->left, curSum, targetSum);
ans += dfs(root->right, curSum, targetSum);
prefix[curSum] -- ;
return ans;
}
int pathSum(TreeNode* root, int targetSum) {
if(root == NULL) return 0;
prefix[0] = 1; // 初始化前缀和
return dfs(root, 0, targetSum);
}
};