(Java)leetcode-437 Path Sum III(路徑總和 III)

題目描述

給定一個二叉樹,它的每個結點都存放着一個整數值。

找出路徑和等於給定數值的路徑總數。

路徑不需要從根節點開始,也不需要在葉子節點結束,但是路徑方向必須是向下的(只能從父節點到子節點)。

二叉樹不超過1000個節點,且節點數值範圍是 [-1000000,1000000] 的整數。

在這裏插入圖片描述

思路1:DFS

本題需要去計算路徑和等於給定數值的路徑總數,遵循樹模型的解題思路,按照遞歸的方式去求解(遞歸的一個重要思想就是兩部分:1.找到最簡單的子問題求解,2.其他問題不考慮內在細節,只考慮整體邏輯),那我們現在來設計遞歸關係:

首先,最簡單的子問題是什麼呢?由於這道題是在樹的框架下,我們最容易想到的就是遍歷的終止條件:

if(root == null){
    return 0;
}

接下來,我們來考慮再上升的一個層次,題目要求 路徑不需要從根節點開始,也不需要在葉子節點結束,但是路徑方向必須是向下的(只能從父節點到子節點) 。這就要求我們只需要去求三部分即可:

  1. 以當前節點作爲頭結點的路徑數量
  2. 以當前節點的左孩子作爲頭結點的路徑數量
  3. 以當前節點的右孩子作爲頭結點的路徑數量

將這三部分之和作爲最後結果即可。

最後的問題是:我們應該如何去求以當前節點作爲頭結點的路徑的數量?這裏依舊是按照樹的遍歷方式模板,採用前序遍歷,每到一個節點讓sum-root.val,並判斷sum是否爲0,如果爲零的話,則找到滿足條件的一條路徑。這條路徑將與該節點左右子樹的統計結果一起相加,往遞歸的上一層返回。這部分對應解法中的pathSumFrom()方法。

代碼如下,形式上是一個“雙遞歸”。

代碼

public class Solution {
    public int pathSum(TreeNode root, int sum) {
        if (root == null) return 0;
        return pathSumFrom(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
    }
    
    private int pathSumFrom(TreeNode node, int sum) {
        if (node == null) return 0;
        return (node.val == sum ? 1 : 0) 
            + pathSumFrom(node.left, sum - node.val) + pathSumFrom(node.right, sum - node.val);
    }
}

在這裏插入圖片描述
空間複雜度: O(n) 遞歸調用棧所花費的空間。
時間複雜度:
最差情況 O(n^2) 無分支
最好情況 O(nlogn) 平衡樹的情況

思路2:回溯

上面的解法由於要經過很多重複的分支,因此耗時較長。
如果能用hashMap來記錄遍歷過程中所經過節點的狀態,就能節省很多不必要的重複遍歷。
下面轉載大神的思路和解法:

這道題用到了一個概念,叫前綴和。就是到達當前元素的路徑上,之前所有元素的和。

前綴和怎麼應用呢?

如果兩個數的前綴總和是相同的,那麼這些節點之間的元素總和爲零。進一步擴展相同的想法, 如果前綴總和currSum,在節點A和節點B處相差target,則位於節點A和節點B之間的元素之和是target

因爲本題中的路徑是一棵樹,從根往任一節點的路徑上(不走回頭路),有且僅有一條路徑,因爲不存在環。(如果存在環,前綴和就不能用了,需要改造算法)

每個節點的前綴和,存儲在hashMap中,key是前綴和, value是大小爲key的前綴和出現的次數。

抵達當前節點(即B節點)後,將前綴和累加,然後查找在前綴和上,有沒有前綴和 currSum-target 的節點(即A節點),存在即表示從A到B有一條路徑之和滿足條件的情況。結果加上滿足前綴和currSum-target的節點的數量。然後遞歸進入左右子樹。

左右子樹遍歷完成之後,回到當前層,需要把當前節點添加的前綴和去除。避免回溯之後影響上一層。因爲思想是前綴和,不屬於前綴的,我們就要去掉它。

時間複雜度:每個節點只遍歷一次,O(N).

空間複雜度:開闢了一個hashMap,O(N).

作者:burning-summer
鏈接:https://leetcode-cn.com/problems/path-sum-iii/solution/qian-zhui-he-di-gui-hui-su-by-shi-huo-de-xia-tian/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

代碼

class Solution {
    public int pathSum(TreeNode root, int sum) {
        // key是前綴和, value是大小爲key的前綴和出現的次數
        Map<Integer, Integer> prefixSumCount = new HashMap<>();
        // 前綴和爲0的一條路徑
        prefixSumCount.put(0, 1);
        // 前綴和的遞歸回溯思路
        return recursionPathSum(root, prefixSumCount, sum, 0);
    }

    /**
     * 前綴和的遞歸回溯思路
     * 從當前節點反推到根節點(反推比較好理解,正向其實也只有一條),有且僅有一條路徑,因爲這是一棵樹
     * 如果此前有和爲currSum-target,而當前的和又爲currSum,兩者的差就肯定爲target了
     * 所以前綴和對於當前路徑來說是唯一的,當前記錄的前綴和,在回溯結束,回到本層時去除,保證其不影響其他分支的結果
     * @param node 樹節點
     * @param prefixSumCount 前綴和Map
     * @param target 目標值
     * @param currSum 當前路徑和
     * @return 滿足題意的解
     */
    private int recursionPathSum(TreeNode node, Map<Integer, Integer> prefixSumCount, int target, int currSum) {
        // 1.遞歸終止條件
        if (node == null) {
            return 0;
        }
        // 2.本層要做的事情
        int res = 0;
        // 當前路徑上的和
        currSum += node.val;

        //---核心代碼
        // 看看root到當前節點這條路上是否存在節點前綴和加target爲currSum的路徑
        // 當前節點->root節點反推,有且僅有一條路徑,如果此前有和爲currSum-target,而當前的和又爲currSum,兩者的差就肯定爲target了
        // currSum-target相當於找路徑的起點,起點的sum+target=currSum,當前點到起點的距離就是target
        res += prefixSumCount.getOrDefault(currSum - target, 0);
        // 更新路徑上當前節點前綴和的個數
        prefixSumCount.put(currSum, prefixSumCount.getOrDefault(currSum, 0) + 1);
        //---核心代碼

        // 3.進入下一層
        res += recursionPathSum(node.left, prefixSumCount, target, currSum);
        res += recursionPathSum(node.right, prefixSumCount, target, currSum);

        // 4.回到本層,恢復狀態,去除當前節點的前綴和數量
        prefixSumCount.put(currSum, prefixSumCount.get(currSum) - 1);
        return res;
    }
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章