LeetCode 力扣 112. 路徑總和

題目描述(簡單難度)

給定一個sum,判斷是否有一條從根節點到葉子節點的路徑,該路徑上所有數字的和等於sum

解法一 遞歸

這道題其實和 111 題 是一樣的,大家可以先看 111 題 的分析,這道題無非是把 111 題 遞歸傳遞的depth改爲了sum的傳遞。

如果不仔細分析題目,代碼可能會寫成下邊的樣子。

public boolean hasPathSum(TreeNode root, int sum) {
    if (root == null) {
        return false;
    }
    return hasPathSumHelper(root, sum);
}

private boolean hasPathSumHelper(TreeNode root, int sum) {
    if (root == null) {
        return sum == 0;
    }
    return hasPathSumHelper(root.left, sum - root.val) || hasPathSumHelper(root.right, sum - root.val);
}

看起來沒什麼問題,並且對於題目給的樣例也是沒問題的。但是對於下邊的樣例:

     3
    / \
   9   20
  /   /  \
 8   15   7

sum = 12

當某個子樹只有一個孩子的時候,就會出問題了,可以看 111 題 的分析。

所以代碼需要寫成下邊的樣子。

public boolean hasPathSum(TreeNode root, int sum) {
    if (root == null) {
        return false;
    }
    return hasPathSumHelper(root, sum);
}

private boolean hasPathSumHelper(TreeNode root, int sum) {
    //到達葉子節點
    if (root.left == null && root.right == null) {
        return root.val == sum;
    }
    //左孩子爲 null
    if (root.left == null) {
        return hasPathSumHelper(root.right, sum - root.val);
    }
    //右孩子爲 null
    if (root.right == null) {
        return hasPathSumHelper(root.left, sum - root.val);
    }
    return hasPathSumHelper(root.left, sum - root.val) || hasPathSumHelper(root.right, sum - root.val);
}

解法二 BFS

同樣的,我們可以利用一個隊列對二叉樹進行層次遍歷。同時還需要一個隊列,保存當前從根節點到當前節點已經累加的和。BFS的基本框架不用改變,參考 102 題。只需要多一個隊列,進行細微的改變即可。

public boolean hasPathSum(TreeNode root, int sum) {
    Queue<TreeNode> queue = new LinkedList<TreeNode>();
    Queue<Integer> queueSum = new LinkedList<Integer>();
    if (root == null)
        return false;
    queue.offer(root);
    queueSum.offer(root.val); 
    while (!queue.isEmpty()) {
        int levelNum = queue.size(); // 當前層元素的個數
        for (int i = 0; i < levelNum; i++) {
            TreeNode curNode = queue.poll();
            int curSum = queueSum.poll();
            if (curNode != null) {
                //判斷葉子節點是否滿足了條件
                if (curNode.left == null && curNode.right == null && curSum == sum) { 
                    return true; 
                }
                //當前節點和累計的和加入隊列
                if (curNode.left != null) {
                    queue.offer(curNode.left);
                    queueSum.offer(curSum + curNode.left.val);
                }
                if (curNode.right != null) {
                    queue.offer(curNode.right);
                    queueSum.offer(curSum + curNode.right.val);
                }
            }
        }
    }
    return false;
}

解法三 DFS

解法一其實本質上就是做了DFS,我們知道DFS可以用棧去模擬。對於這道題,我們可以像解法二的BFS一樣,再增加一個棧,去保存從根節點到當前節點累計的和就可以了。

這裏的話,用DFS裏的中序遍歷,參考 94 題

public boolean hasPathSum(TreeNode root, int sum) {
    Stack<TreeNode> stack = new Stack<>();
    Stack<Integer> stackSum = new Stack<>();
    TreeNode cur = root;
    int curSum = 0;
    while (cur != null || !stack.isEmpty()) {
        // 節點不爲空一直壓棧
        while (cur != null) {
            stack.push(cur);
            curSum += cur.val;
            stackSum.push(curSum);
            cur = cur.left; // 考慮左子樹
        }
        // 節點爲空,就出棧
        cur = stack.pop();
        curSum = stackSum.pop();
        //判斷是否滿足條件
        if (curSum == sum && cur.left == null && cur.right == null) {
            return true;
        }
        // 考慮右子樹
        cur = cur.right;
    }
    return false;
}

但是之前講了,對於這種利用棧完全模擬遞歸的思路,對時間複雜度和空間複雜度並沒有什麼提高。只是把遞歸傳遞的參數rootsum,本該由計算機自動的壓棧出棧,由我們手動去壓棧出棧了。

所以我們能不能提高一下,比如省去sum這個棧?讓我們來分析以下。參考 這裏

我們如果只用一個變量curSum來記錄根節點到當前節點累計的和,有節點入棧就加上節點的值,有節點出棧就減去節點的值。

比如對於下邊的樹,我們進行中序遍歷。

     3
    / \
   9   20
  / \   
 8   15   

curSum = 0
3 入棧, curSum = 33
9 入棧, curSum = 123 -> 9
8 入棧, curSum = 203 -> 9 -> 8
8 出棧, curSum = 123 -> 9
9 出棧, curSum = 315 入棧, curSum = 183 -> 9 -> 15

此時路徑是 3 -> 9 -> 15,和應該是 27。但我們得到的是 18,少加了 9

原因就是我們進行的是中序遍歷,當我們還沒訪問右邊的節點的時候,根節點已經出棧了,再訪問右邊節點的時候,curSum就會少一個根節點的值。

所以,我們可以用後序遍歷,先訪問左子樹,再訪問右子樹,最後訪問根節點。再看一下上邊的問題。

     3
    / \
   9   20
  / \   
 8   15   

curSum = 0
3 入棧, curSum = 33
9 入棧, curSum = 123 -> 9
8 入棧, curSum = 203 -> 9 -> 8
8 出棧, curSum = 123 -> 9
15 入棧, curSum = 273 -> 9 -> 15

此時路徑 3 -> 9 -> 15 對應的 curSum 就是正確的了。

用棧實現後序遍歷,比中序遍歷要複雜一些。當訪問到根節點的時候,它的右子樹可能訪問過了,那就把根節點輸出。它的右子樹可能沒訪問過,我們需要去遍歷它的右子樹。所以我們要用一個變量pre保存上一次遍歷的節點,用來判斷當前根節點的右子樹是否已經遍歷完成。

public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> result = new LinkedList<>();
    Stack<TreeNode> toVisit = new Stack<>();
    TreeNode cur = root;
    TreeNode pre = null;

    while (cur != null || !toVisit.isEmpty()) {
        while (cur != null) {
            toVisit.push(cur); // 添加根節點
            cur = cur.left; // 遞歸添加左節點
        }
        cur = toVisit.peek(); // 已經訪問到最左的節點了
        // 在不存在右節點或者右節點已經訪問過的情況下,訪問根節點
        if (cur.right == null || cur.right == pre) {
            toVisit.pop();
            result.add(cur.val);
            pre = cur;
            cur = null;
        } else {
            cur = cur.right; // 右節點還沒有訪問過就先訪問右節點
        }
    }
    return result;
}

有了上邊的後序遍歷,對於這道題,代碼就很好改了。

public boolean hasPathSum(TreeNode root, int sum) { 
    Stack<TreeNode> toVisit = new Stack<>();
    TreeNode cur = root;
    TreeNode pre = null;
    int curSum = 0; //記錄當前的累計的和
    while (cur != null || !toVisit.isEmpty()) {
        while (cur != null) {
            toVisit.push(cur); // 添加根節點
            curSum += cur.val;
            cur = cur.left; // 遞歸添加左節點
        }
        cur = toVisit.peek(); // 已經訪問到最左的節點了
        //判斷是否滿足條件
        if (curSum == sum && cur.left == null && cur.right == null) {
            return true;
        }
        // 在不存在右節點或者右節點已經訪問過的情況下,訪問根節點
        if (cur.right == null || cur.right == pre) {
            TreeNode pop = toVisit.pop();
            curSum -= pop.val; //減去出棧的值
            pre = cur;
            cur = null;
        } else {
            cur = cur.right; // 右節點還沒有訪問過就先訪問右節點
        }
    }
    return false;
}

這道題還是在考二叉樹的遍歷,DFSBFS。解法三通過後序遍歷節省了sum棧,蠻有意思的。

更多詳細通俗題解詳見 leetcode.wang

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