--- title: "刷題模式: 深度優先搜索(Depth-First Search, DFS)" description: "Patterns for Coding Questions: Depth-First Search." image: https://i.imgur.com/Wnnr2qC.png author: Hsins tags: LeetCode, Coding Interview robots: breaks: GA: disqus: --- # 深度優先搜索(Depth-First Search, DFS) <p style="text-align: center"> <img src="https://i.imgur.com/Wnnr2qC.png" height=200/> </p> > 本篇內容主要為 [Grokking the Coding Interview: Patterns for Coding Questions](https://www.educative.io/courses/grokking-the-coding-interview) 的翻譯與整理,行有餘力建議可以購買該專欄課程閱讀並在上面進行練習。 ## 重點整理 - 深度優先搜索(Depth-First Search, DFS) - 策略:使用 **遞迴(recursion)** 或透過 **堆棧(stack)** 追蹤遍歷過程的父節點 - 題型: - 樹的自根節點到葉節點遍歷 - 樹的父子路徑相關操作 ## 題目匯總 - `0018` Diameter of a Binary Tree - `0020` Lowest Common Ancestor of a Binary Tree - `0100` Same Tree - `0104` Maximum Depth of Binary Tree - `0110` Balanced Binary Tree - `0111` Minimum Depth of Binary Tree - `0112` Path Sum - `0113` Path Sum II - `0129` Sum Root to Leaf Numbers - `0133` Clone Graph - `0200` Number of Islands - `0257` Binary Tree Paths - `0437` Path Sum III - `0897` Increasing Order Search Tree ## [例題] Binary Tree Path Sum ### 問題 給定一棵二元樹(binary tree)與一個數字 $S$,找出該二元樹中是否存在一條自根節點到葉子節點的路徑,滿足路徑上節點數值總和恰好等於 $S$。 **Example 01** <table> <tr> <th>說明</th> <th>二元樹</th> </tr> <tr> <td> ``` Input : S = 10 Output : TRUE ``` </td> <td> <p style="text-align: center"> <img src="https://i.imgur.com/Z0X5BIr.png" /> </p> </td> </tr> </table> **Example 02** <table> <tr> <th>陣列</th> <th>二元樹</th> </tr> <tr> <td> ``` Input : S = 23 Output : TRUE ``` ``` Input : S = 16 Output : FALSE ``` </td> <td> <p style="text-align: center"> <img src="https://i.imgur.com/X29ykKm.png" /> </p> </td> </tr> </table> ### 題解 由於我們想要 ==搜索自根結點到葉子節點的路徑==,因此採用 **深度優先搜索(Depth-First Search, DFS)** 技巧。 為了遞迴遍歷二元樹,我們可以自根節點開始,呼叫兩個遞迴函數分別處理左子節點和右子節點。具體演算法的步驟如下: 1. 自根結點 `root` 開始進行深度優先搜索 2. 如果當前節點並不是葉子節點,進行以下操作: - 將路徑和減去當前節點的值,即 `S = S - node.val` - 呼叫遞迴函數,處理當前節點的左子節點和右子節點,注意子節點須滿足的路經和為前一步驟所得到的 `S = S - node.val` 3. 如果當前節點是葉子節點,且其值滿足路徑和 $S$,即找到滿足條件的路徑,返回 `true` 4. 如果當前節點是葉子節點,但其值不滿足路徑和 $S$ 不同,返回 `talse` 上述步驟如下圖所示: <p style="text-align: center"> <small>1. 自根結點 <code>root</code> 開始進行深度優先搜索</small> <img src="https://i.imgur.com/0GEzyzN.png"> <small>2. 當前節點並非葉子節點,呼叫遞迴函數處理左右子節點,遞迴時 <code>S = 23 - 12 = 11</code></small> <img src="https://i.imgur.com/lD6Yqha.png"> <small>3. 當前節點並非葉子節點,呼叫遞迴函數處理左右子節點,遞迴時 <code>S = 11 - 7 = 4</code></small> <img src="https://i.imgur.com/nKNIXs6.png"> <small>4. 當前節點已為葉子節點,但不滿足 <code>S = 9</code>,故返回 <code>false</code></small> <img src="https://i.imgur.com/kd0vNuT.png"> <small>5. 節點 <code>7</code> 的左子樹遞迴遍歷完畢,遍歷右子樹;由於右子樹為 <code>null</code>,故返回 <code>false</code></small> <img src="https://i.imgur.com/CDbfEKI.png"> <small>6. 節點 <code>12</code> 的左子樹遞迴遍歷完畢,遍歷右子樹,遞迴時 <code>S = 23 - 12 = 11</code></small> <img src="https://i.imgur.com/uscy8ia.png"> <small>7. 當前節點並非葉子節點,呼叫遞迴函數處理左右子節點,遞迴時 <code>S = 11 - 1 = 10</code></small> <img src="https://i.imgur.com/niGa6bp.png"> <small>8. 當前節點已為葉子節點,並且滿足 <code>S = 10</code>,故返回 <code>true</code></small> <img src="https://i.imgur.com/DDPuA8y.png"> <small>9. 左子節點滿足條件,返回 <code>true</code></small> <img src="https://i.imgur.com/DDPuA8y.png"> <small>10. 右子節點滿足條件,返回 <code>true</code></small> <img src="https://i.imgur.com/EM2tax7.png"> </p> ### 代碼 #### C++ ``` cpp class TreePathSum { public: static bool hasPath(TreeNode *root, int sum) { if (root == nullptr) return false; if (root->val == sum && root->left == nullptr && root->right == nullptr) return true; return hasPath(root->left, sum - root->val) || hasPath(root->right, sum - root->val); } } ``` #### Java ``` java class TreePathSum { public static boolean hasPaht(TreeNode root, int sum) { if (root == null) return false; // if the current node is a leaf and its value is equal to the sum if (root.val == sum && root.left == null && root.right == null) return true; // recursively call to traverse the left and right sub-tree return hasPath(root.left, sum - root.val) || hasPath(root.right, sum - root.val); } } ``` #### JavaScript ``` javascript const hasPath = (root, sum) => { // 邊界條件:若為空二元樹,返回 false if (root === null) return false; // 判斷當前節點是否為葉子節點,若為葉子節點且滿足路徑和,返回 true if (root.val === sum && root.left === null && root.right === null) return true; // 當前節點不滿足條件,繼續遞迴遍歷左右子節點 return hasPath(root.left, sum - root.val) || hasPath(root.right, sum - root.val); } ``` #### Python ``` python def has_path(root, sum): if root is None: return False # if the current node is a leaf and its value is equal to the sum, we've found the path if root.val == sum and root.left is None and root.right is None: return True # recursively call to traverse the left and right sub-tree return has_path(root.left, sum - root.val) or has_path(root.right, sum - root.val) ``` ### 分析 - 時間複雜度:$\mathcal{O}(n)$,其中 $n$ 為二元樹中的節點總數 - 空間複雜度:$\mathcal{O}(n)$,該空間被用來儲存遞迴堆棧(recursion stack),最糟狀況是當給定二元樹為一個鏈表時,即所有節點都只有一個子節點 ## [例題] All Paths for a Sum ### 問題 給定一棵二元樹(binary tree)與一個數字 $S$,找出該二元樹中所有自根節點到葉子節點總和等於 $S$ 的路徑。 **Example 01** <table> <tr> <th>說明</th> <th>二元樹</th> </tr> <tr> <td> ``` Input : S = 12 Output : 2 // 1 + 7 + 4 = 12 // 1 + 9 + 2 = 12 ``` </td> <td> <p style="text-align: center"> <img src="https://i.imgur.com/yeQHo40.png" /> </p> </td> </tr> </table> **Example 02** <table> <tr> <th>說明</th> <th>二元樹</th> </tr> <tr> <td> ``` Input : S = 23 Output : 2 // 12 + 7 + 4 = 23 // 12 + 1 + 10 = 23 ``` </td> <td> <p style="text-align: center"> <img src="https://i.imgur.com/QODj3Sj.png" /> </p> </td> </tr> </table> ### 題解 這一題遵循 **深度優先搜索(Depth-First Search, DFS)** 策略,注意以下幾點: - 每次找到滿足條件的路徑時,將其儲存於一個陣列中 - 我們需要遍歷所有路徑,不能在找到第一條路徑時就停止搜索 ### 代碼 #### C++ ``` cpp class FindAllTreePaths { public: static vector<vector<int>> findPaths(TreeNode *root, int sum) { vector<vector<int>> allPaths; vector<int> currentPath; findPathRecursive(root, sum, currentPath, allPaths); return allPaths; } private: static void findPathsRecursive(TreeNode *currentNode, int sum, vector<int> &currentPath, vector<vector<int>> &allPaths) [ if (currentNode == nullptr) return; currentPath.push_back(currentNode->val); if (currentNode->val == sum && currentNode->left == nullptr && currentNode->right == nullptr) { allPaths.push_back(vector<int>(currentPath)); } else { findPathsRecursive(currentNode->left, sum - currentNode->val, currentPath, allPaths); findPathsRecursive(currentNode->right, sum - currentNode->val, currentPath, allPaths); } currentPath.pop_back(); ] } ``` #### Java ``` java class FindAllTreePaths { public static List<List<Integer>> findPaths(TreeNode root, int sum) { List<List<Integer>> allPaths = new ArrayList<>(); List<Integer> currentPath = new ArrayList<Integer>(); findPathsRecursive(root, sum, currentPath, allPaths); return allPaths; } private static void findPathsRecursive(TreeNode currentNode, int sum, List<Integer> currentPath, List<List<Integer>> allPaths) { if (currentNode == null) return; // add the current node to the path currentPath.add(currentNode.val); // if current node is a leaf and its value is equal to sum, save the current path if (currentNode.val == sum && currentNode.left == null && currentNode.right == null) { allPaths.add(new ArrayList<Integer>(currentPath)); } else { // traverse the left sub-tree findPathsRecursive(currentNode.left, sum - currentNode.val, currentPath, allPaths); // traverse the right sub-tree findPathsRecursive(currentNode.right, sum - currentNode.val, currentPath, allPaths); } // remove the current node from the path to backtrack // we need to remove the current node while we're gogin up the recursive call stack currentPath.remove(currentPath.size() - 1); } } ``` #### JavaScript ``` javascript const findPaths = (root, sum) => { let allPaths = []; findPathsRecursive(root, sum, new Deque(), allPaths); return allPaths; } const findPathsRecursive = (currentNode, sum, currentPath, allPaths) => { if (currentNode == null) return; // 添加當前節點到路徑中 currentPath.push(currentNode.val); // 如果當前節點為葉子節點,且滿足路徑和,紀錄當前路徑 if (currentNode.val === sum && currentNode.left === null && currentNode.right === null) { allPaths.push(currentPath.toArray()); } else { findPathsRecursive(currentNode.left, sum - currentNode.val, currentPath, allPaths); findPathsRecursive(currentNode.right, sum - currentNode.val, currentPath, allPaths); } // 將當前節點移出路徑 currentPath.pop(); } ``` #### Python ``` python def find_paths(root, sum): all_paths = [] find_paths_recursive(root, sum, [], all_paths) return all_paths def find_paths_recursive(current_node, sum, current_path, all_paths): if current_node is None: return current_path.append(current_node.val) if current_node.val == sum and current_node.left is None and current_node.right is None: all_paths.append(list(current_node)) else: find_paths_recursive(current_node.left, sum - current_node.val, current_path, all_paths) find_paths_recursive(current_node.right, sum - current_node.val, current_path, all_paths) del current_path[-1] ``` ### 分析 - 時間複雜度:$\mathcal{O}(n \log{n})$,其中 $n$ 為二元樹中的節點總數;遍歷所有節點需要花費 $\mathcal{O}(n)$,而在每個葉子節點,需要複製 $\mathcal{O}(\log{n})$ 個結點來儲存其路徑 - 空間複雜度:$\mathcal{O}(n \log{n})$,其中需要 $\mathcal{O}(n)$ 儲存遞迴堆棧(recursion stack);而儲存所有路徑需要 $\mathcal{O}(n \log{n})$ - 對於二元樹而言,每個葉子節點只有一條路徑可以到達,亦即路徑總數不超過葉子節點數量,因此最大元素數為 $\mathcal{O}(\frac{n}{2}) = \mathcal{O}(n)$ - 每一條路徑包含多個節點,對於平衡二元樹來說,每個葉子節點都位在最大深度,而平衡二元樹的深度是 $\mathcal{O}(\log{n})$ ## [例題] Sum of Path Numbers ### 問題 給定一棵二元樹(binary tree),其中節點的值只可能為 `0 - 9`,每一條路徑代表一個數字,計算所有數字的總和。 **Example 01** <table> <tr> <th>說明</th> <th>二元樹</th> </tr> <tr> <td> ``` Output : 408 // 說明 17 + 192 + 199 = 408 ``` </td> <td> <p style="text-align: center"> <img src="https://i.imgur.com/Wb0dfF3.png" /> </p> </td> </tr> </table> **Example 02** <table> <tr> <th>說明</th> <th>二元樹</th> </tr> <tr> <td> ``` Output : 332 // 說明 101 + 116 + 115 = 332 ``` </td> <td> <p style="text-align: center"> <img src="https://i.imgur.com/mxenHY9.png" /> </p> </td> </tr> </table> ### 題解 這一題遵循 **深度優先搜索(Depth-First Search, DFS)** 策略,在過程中需要 ==追蹤當前路徑表示的數字==。 那麼我們要如何計算目前路徑所代表的數字呢?比如前面範例中,我們正處在 `7` 這個節點時,當前路徑所代表的數字為 `17 = 1x10 + 7`。 ### 代碼 #### C++ ``` cpp class SumOfPathNumbers { public: static int findSumOfPathNumbers(TreeNode *root) { return findRootToLeafPathNumbers(root, 0); } private: static int findRootToLeafPathNumbers(TreeNode *root, int pathSum) { if (currentNode == nullptr) return 0; // calculate the path number of the current node pathSum = 10 * pathSum + currentNode->val; // if the current node is a leaf, return the current path sum if (currentNode->left == nullptr && currentNode->right == nullptr) return pathSum; // traverse the left and right sub-tree return findRootToLeafPathNumbers(currentNode->left, pathSum) + findRootToLeafPathNumbers(currentNode->right, pathSum); } } ``` #### Java ``` java class SumOfPathNumbers { public static int findSumOfPathNumbers(TreeNode root) { return findRootToLeafPathNumbers(root, 0); } private static int findRootToLeafPathNumbers(TreeNode currentNode, int pathSum) { if (currentNode == null) return 0; // calculate the path number of the current node pathSum = 10 * pathSum + currentNode.val; // if current node is a leaf, return the current path sum if (currentNode.left == null && currentNode.right == null) return pathSum; // traverse the left and right sub-tree return findRootToLeafPathNumbers(currentNode.left, pathSum) + findRootToLeafPathNumbers(currentNode.right, pathSum) } } ``` #### JavaScript ``` javascript const findSumOfPathNumbers = (root) => { return findRootToLeafPathNumbers(root, 0); } const findRootToLeafPathNumbers = (currentNode, pathSum) => { if (currentNode === null) return 0; // 計算當前節點表示的數字 pathSum = 10 * pathSum + currentNode.val; // 如果當前節點為葉子節點,返回當前路徑數字 if (currentNode.left === null & currentNode.right === null) return pathSum; // 遞迴遍歷左右子樹 retrun findRootToLeafPathNumbers(currentNode.left, pathSum) + findRootToLeafPathNumbers(currentNode.right, pathSum); } ``` #### Python ``` python def find_sum_of_path_numbers(root): return find_root_to_leaf_path_numbers(root, 0) def find_root_to_leaf_path_numbers(current_node, path_sum): if current_node is None: return 0 # calculate the path number of the current node path_sum = 10 * path_sum + current_node.val # if the current node is a leaf, return the current path sum if current_node.left is None and current_node.right is None: return path_sum # traverse the left and the right sub-tree return find_root_to_leaf_path_numbers(current_node.left, path_sum) + find_root_to_leaf_path_numbers(current_node.right, path_sum) ``` ### 分析 - 時間複雜度:$\mathcal{O}(n)$,其中 $n$ 為二元樹中的節點總數 - 空間複雜度:$\mathcal{O}(n)$,該空間被用來儲存遞迴堆棧(recursion stack),最糟狀況是當給定二元樹為一個鏈表時,即所有節點都只有一個子節點 ## [例題] Path with Given Sequence ### 問題 給定一棵二元樹(binary tree)與一個整數陣列,判斷該整數陣列是不是樹中的一條路徑。 **Example 01** <table> <tr> <th>說明</th> <th>二元樹</th> </tr> <tr> <td> ``` Input : [1, 9, 9] Output : True ``` </td> <td> <p style="text-align: center"> <img src="https://i.imgur.com/fqqTjDQ.png" /> </p> </td> </tr> </table> **Example 02** <table> <tr> <th>說明</th> <th>二元樹</th> </tr> <tr> <td> ``` Input : [1, 1, 6] Output : True ``` ``` Input : [1, 0, 7] Output : False ``` </td> <td> <p style="text-align: center"> <img src="https://i.imgur.com/RqqwsA8.png" /> </p> </td> </tr> </table> ### 題解 這一題遵循 **深度優先搜索(Depth-First Search, DFS)** 策略,在過程中需要 ==判斷當前數值是否與陣列元素相符,一但不滿足可以儘早返回==。 ### 代碼 #### C++ ``` cpp class PathWithGivenSequence { public: static bool findPath(TreeNode *root, const vector<int> &sequence) { if (root === nullptr) return sequence.empty(); return findPathRecursive(root, sequence, 0); } private: static bool findPathRecursive(TreeNode *currentNode, const vector<int> &sequence, int sequenceIndex) { if (currentNode == nullptr) return false; if (sequenceIndex >= sequence.size() || currentNode->val != sequence[sequenceIndex]) return false; if (currentNode->left == nullptr && currentNode->right == nullptr && sequenceIndex == sequence.size() - 1) return true; return findPathRecursive(currentNode->left, sequence, sequenceIndex + 1) || findPathRecursive(currentNode->right, sequence, sequenceIndex + 1); } } ``` #### Java ``` java class PathWithGivenSequence { public static boolean findPath(TreeNode root, int[] sequence) { if (root == null) return sequence.length == 0; return findPathRecursive(root, sequence, 0); } private static boolean findPathRecursive(TreeNode currentNode, int[] sequence, int sequenceIndex) { if (currentNode == null) return false; if (sequenceIndex >= sequence.length || currentNode.val != sequence[sequenceIndex]) retun false; if (currentNode.left == null && currentNode.right == null && sequenceIndex == sequence.length - 1) return true; return findPathRecursive(currentNode.left, sequence, sequenceIndex + 1) || findPathRecursive(currentNode.right, sequence, sequenceIndex + 1); } } ``` #### JavaScript ``` javascript const findPath = (root, sequence) => { if (root === null) return sequence.length === 0; return findPathRecursive(root, sequence, 0); } const findPathRecursive = (currentNode, sequence, sequenceIndex) => { if (currentNode === null) return false; const sequenceLength = sequence.length; if (sequenceIndex >= sequenceLength || currentNode.val !== sequence[sequenceIndex]) return false; if (currentNode.left === null && currentNode.right === null && sequenceIndex === sequenceLength - 1) return true; return findPathRecursive(currentNode.left, sequence, sequenceIndex + 1) || findPathRecursive(currentNode.right, sequence, sequenceIndex + 1) } ``` #### Python ``` python def find_path(root, sequence): if not root: return len(sequence) == 0 return find_path_recursive(root, sequence, 0) def find_path_recursive(current_node, sequence, sequence_index): if current_node is None: return False sequence_length = len(sequence) if sequence_index >= sequence_length or current_node.val != sequence[sequence_index]: return False if current_node.left is None and current_node.right is None and sequence_index == sequence_length - 1: return True return find_path_recursive(current_node.left, sequence, sequence_index + 1) or find_path_recursive(current_node.right, sequence, sequence_index + 1) ``` ### 分析 - 時間複雜度:$\mathcal{O}(n)$,其中 $n$ 為二元樹中的節點總數 - 空間複雜度:$\mathcal{O}(n)$,該空間被用來儲存遞迴堆棧(recursion stack),最糟狀況是當給定二元樹為一個鏈表時,即所有節點都只有一個子節點 ## [例題] Count Paths for a Sum ### 問題 給定一棵二元樹(binary tree)與一個數字 $S$,找出該二元樹中所有總和等於 $S$ 的路徑,注意此處的路徑並 **不要求自根結點到葉子節點,只需要遵循父節點到子節點的方向即可**。 **Example 01** <table> <tr> <th>說明</th> <th>二元樹</th> </tr> <tr> <td> ``` Input : S = 12 Output : 3 // 7 + 5 = 12 // 1 + 9 + 2 = 12 // 9 + 3 = 12 ``` </td> <td> <p style="text-align: center"> <img src="https://i.imgur.com/lT116p1.png" /> </p> </td> </tr> </table> **Example 02** <table> <tr> <th>說明</th> <th>二元樹</th> </tr> <tr> <td> ``` Input : S = 11 Output : 2 // 7 + 4 = 11 // 1 + 10 = 11 ``` </td> <td> <p style="text-align: center"> <img src="https://i.imgur.com/IIqoKqW.png" /> </p> </td> </tr> </table> ### 題解 這一題遵循 **深度優先搜索(Depth-First Search, DFS)** 策略,注意以下幾點: 1. 必須使用一個陣列追蹤當前路徑,並傳遞給後續的遞迴函數 2. 當遍歷一個節點時,需要執行兩件事: - 添加當前節點到當前路徑中 - 計算路徑陣列中以當前節點為結束元素的路徑和,若滿足 $S$ 則將計數 `count` 加一 3. 必須遍歷所有路徑,不能在找到條件的路徑時就結束 4. 在返回時必須自路徑中移除當前節點,因為還必須遞迴遍歷其他路徑,需要進行回溯(backtrack) ### 代碼 #### C++ ``` cpp class CountAllPathSum { public: static int countPaths(TreeNode *root, int S) { vector<int> currentPath; return countPathsRecursive(root, S, currentPath); } private: static int countPathsRecursive(TreeNode *currentNode, int S, vector<int> &currentPath) { if (currentNode == nullptr) return 0; // add the current node to the path currentPath.push_back(currentNode->val); int pathCount = 0; int pathSUm = 0; // find the sum of all sub-paths in the current path for (vector<int>::reverse_iterator itr = currentPath.rbegin(); itr != currentPath.rend(); ++itr) { pathum += *itr; if (pathSum == S) pathCount++; } // traverse the left and right sub-trees pathCount += countPathsRecursive(currentNode->left, S, currentPath); pathCount += countPathsRecursive(currentNode->right, S, currentPath); // remove the current node currentPath.pop_back(); return pathCount; } } ``` #### Java ``` java class CountAllPathSum { public static int countPaths(TreeNode root, int S) { List<Integer> currentPath = new LinkedList<>(); return countPathsRecursive(root, S, currentPath); } private static int countPathsRecursive(TreeNode currentNode, int S, List<Integer> currentPath) { if (currentNode == null) return 0; // add current node to the path currentPath.add(currentNode.val); int pathCount = 0; int pathSum = 0; // find the sums of all sub-paths in the current path list ListIterator<Integer> pathIterator = currentPath.ListIterator(currentPath.size()); while (pathIterator.hasPrevious()) { pathSum += pathIterator.previous(); if (pathSum == S) pathCount++; } // traverse the left and right sub-tree pathCount += countPathsRecursive(currentNode.left, S, currentPath); pathCount += countPathsRecursive(currentNode.right, S, currentPath); // remove current node from the path to backtrack currentPath.remove(currentPath.size() - 1); return pathCount; } } ``` #### JavaScript ``` javascript const countPaths = (root, S) => { return countPathsRecursive(root, S, new Deque()); } const countPathsRecursive = (currentNode, S, currentPath) => { if (currentNode === null) return 0; // add current node to the path currentPath.push(currentNode.val); let pathCount = 0; let pathSum = 0; // find the sum of all sub-paths in current path for (let i = currentPath.length - 1; i >= 0; i--) { pathSum += currentPath[i]; if (pathSum === S) pathCount += 1; } // traverse left and right sub-trees pathCount += countPathsRecursive(currentNode.left, S, currentPath); pathCount += countPathsRecursive(currentNode.right, S, currentPath); // remove current node for backtracking currentPath.pop(); return pathCount; } ``` #### Python ``` python def count_paths(root, S): return count_paths_recursive(root, S, []) def count_paths_recursive(current_node, S, current_path): if current_node is None: return 0 # add current node to the path current_path.append(current_node.val) path_count = 0 path_sum = 0 # find the sum of all sub-paths in current path list for i in range(len(current_path) - 1, -1, -1): path_sum += current_path[i] if path_sum == S: path_count += 1 # traverse left and right sub-tree path_count += count_paths_recursive(current_node.left, S, current_path) path_count += count_paths_recursive(current_node.right, S, current_path) del current_path[-1] return path_count ``` ### 分析 - 時間複雜度:遍歷所有節點需要 $\mathcal{O}(n)$,每一個節點都必須迭代當前路徑,此時: - 最差狀況在傾斜樹(skewed tree)時的路徑需要 $\mathcal{O}(n)$,故為 $\mathcal{O}(n^2)$ - 最優狀況在平衡樹(balanced tree)時的路徑需要 $\mathcal{O}(\log{n})$,故為 $\mathcal{O}(n \log{n})$ - 空間複雜度:$\mathcal{O}(n)$,該空間被用來儲存遞迴堆棧(recursion stack),最糟狀況是當給定二元樹為一個鏈表時,即所有節點都只有一個子節點;除此之外還需要有 $\mathcal{O}(n)$ 儲存 `currentPath`。故 $\mathcal{O}(n + n) = \mathcal{O}(2n) = \mathcal{O}(n)$ ## 參考資料 - [Merge Overlapping Intervals | GeeksforGeeks](https://www.geeksforgeeks.org/merging-intervals/) - [Coding Patterns: Depth First Search (DFS) | emre.me](https://emre.me/coding-patterns/depth-first-search/) - [Depth First Search Pattern | Astik Anand](https://astikanand.github.io/techblogs/coding-problem-patterns/depth-first-search-pattern)