You are given a binary tree in which each node contains an integer value.
Find the number of paths that sum to a given value.
The path does not need to start or end at the root or a leaf, but it must go downwards (traveling only from parent nodes to child nodes).
The tree has no more than 1,000 nodes and the values are in the range -1,000,000 to 1,000,000.
Example:
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8 10 / \ 5 -3 / \ \ 3 2 11 / \ \ 3 -2 1 Return 3. The paths that sum to 8 are: 1. 5 -> 3 2. 5 -> 2 -> 1 3. -3 -> 11
這道題讓我們求二叉樹的路徑的和等於一個給定值,說明了這條路徑不必要從根節點開始,可以是中間的任意一段,而且二叉樹的節點值也是有正有負。那么可以用遞歸來做,相當於先序遍歷二叉樹,對於每一個節點都有記錄了一條從根節點到當前節點到路徑,同時用一個變量 curSum 記錄路徑節點總和,然后看 curSum 和 sum 是否相等,相等的話結果 res 加1,不等的話繼續查看子路徑和有沒有滿足題意的,做法就是每次去掉一個節點,看路徑和是否等於給定值,注意最后必須留一個節點,不能全去掉了,因為如果全去掉了,路徑之和為0,而如果給定值剛好為0的話就會有問題,整體來說不算一道很難的題,參見代碼如下:
解法一:
class Solution { public: int pathSum(TreeNode* root, int sum) { int res = 0; vector<TreeNode*> out; helper(root, sum, 0, out, res); return res; } void helper(TreeNode* node, int sum, int curSum, vector<TreeNode*>& out, int& res) { if (!node) return; curSum += node->val; out.push_back(node); if (curSum == sum) ++res; int t = curSum; for (int i = 0; i < out.size() - 1; ++i) { t -= out[i]->val; if (t == sum) ++res; } helper(node->left, sum, curSum, out, res); helper(node->right, sum, curSum, out, res); out.pop_back(); } };
我們還可以對上面的方法進行一些優化,來去掉一些不必要的計算,可以用 HashMap 來建立路徑之和跟其個數之間的映射,即路徑之和為 curSum 的個數為 m[curSum],這里需要將 m[0] 初始化為1,后面會講解原因。在遞歸函數中,首先判空,若為空,直接返回0。然后就是 curSum 加上當前結點值。由於此時 curSum 可能已經大於了目標值 sum,所以用 curSum 減去 sum,並去 HashMap 中查找這個差值的映射值,若映射值大於0的化,說明存在結束點為當前結點且和為 sum 的路徑,這就相當於累加和數組快速求某個區域和的原理。當 curSum 等於 sum 的時候,表明從根結點到當前結點正好是一條符合要求的路徑,此時 res 應該大於0,這就是為啥要初始化 m[0] 為1的原因,舉個例子來說吧,看下面這棵樹:
1 / 2 / 1
假設 sum=3,當遍歷根結點1時,curSum 為1,那么 curSum-sum=-2,映射值為0,然后建立映射 m[1]=1。此時遍歷結點2,curSum 為3,那么 curSum-sum=0,由於 m[0] 初始化為1,所以 res=1,這是 make sense 的,因為存在和為3的路徑。此時再遍歷到第二個結點1,curSum 為4,那么 curSum-sum=1,由於之前建立了 m[1]=1 的映射,所以當前的 res 也為1,因為存在以第二個結點1為結尾且和為3的路徑,那么遞歸返回到結點2時候,此時的 res 累加到了2,再返回根結點1時,res 就是2,最終就返回了2,那么可以看出遞歸函數的意義,某個結點的遞歸函數的返回值就是該結點上方或下方所有和為 sum 的路徑總個數,參見代碼如下:
解法二:
class Solution { public: int pathSum(TreeNode* root, int sum) { unordered_map<int, int> m; m[0] = 1; return helper(root, sum, 0, m); } int helper(TreeNode* node, int sum, int curSum, unordered_map<int, int>& m) { if (!node) return 0; curSum += node->val; int res = m[curSum - sum]; ++m[curSum]; res += helper(node->left, sum, curSum, m) + helper(node->right, sum, curSum, m); --m[curSum]; return res; } };
下面這種方法非常的簡潔,用了兩個遞歸函數,其中的一個 sumUp 遞歸函數是以當前結點為起點,和為 sum 的路徑個數,采用了前序遍歷,對於每個遍歷到的節點進行處理,維護一個變量 pre 來記錄之前路徑之和,然后 cur 為 pre 加上當前節點值,如果 cur 等於 sum,那么返回結果時要加1,然后對當前節點的左右子節點調用遞歸函數求解,這是 sumUp 遞歸函數。而在 pathSum 函數中,我們對當前結點調用 sumUp 函數,加上對左右子結點調用 pathSum 遞歸函數,三者的返回值相加就是所求,參見代碼如下:
解法三:
class Solution { public: int pathSum(TreeNode* root, int sum) { if (!root) return 0; return sumUp(root, 0, sum) + pathSum(root->left, sum) + pathSum(root->right, sum); } int sumUp(TreeNode* node, int pre, int& sum) { if (!node) return 0; int cur = pre + node->val; return (cur == sum) + sumUp(node->left, cur, sum) + sumUp(node->right, cur, sum); } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/437
類似題目:
參考資料:
https://leetcode.com/problems/path-sum-iii/
https://leetcode.com/problems/path-sum-iii/discuss/91889/Simple-Java-DFS
https://leetcode.com/problems/path-sum-iii/discuss/91878/17-ms-O(n)-java-Prefix-sum-method