LeetCode 第113题:路径总和 II

90 阅读9分钟

LeetCode 第113题:路径总和 II

题目描述

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

难度

中等

题目链接

点击在LeetCode中查看题目

示例

示例 1:

示例1

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]

示例 2:

示例2

输入:root = [1,2,3], targetSum = 5
输出:[]

示例 3:

输入:root = [1,2], targetSum = 0
输出:[]

提示

  • 树中节点总数在范围 [0, 5000]
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

解题思路

方法一:深度优先搜索(DFS)+ 回溯

这道题是第112题的扩展,不仅需要判断是否存在满足条件的路径,还需要找出所有满足条件的路径。我们可以使用深度优先搜索(DFS)结合回溯的方法来解决。

关键点:

  • 使用一个路径列表记录当前遍历的路径
  • 使用回溯法遍历所有可能的路径
  • 当到达叶子节点且路径和等于目标和时,将当前路径添加到结果中
  • 注意在回溯时需要移除当前节点

具体步骤:

  1. 定义一个结果列表和一个当前路径列表
  2. 从根节点开始递归遍历
  3. 将当前节点添加到路径中
  4. 如果当前节点是叶子节点且路径和等于目标和,将当前路径添加到结果中
  5. 递归遍历左右子树
  6. 回溯时从路径中移除当前节点
  7. 返回所有满足条件的路径

时间复杂度:O(n²),其中n是树中节点的数量,最坏情况下需要遍历所有节点,且每找到一条路径都需要O(n)的时间复制路径 空间复杂度:O(n),递归调用栈的深度和路径列表的空间

方法二:广度优先搜索(BFS)

我们也可以使用广度优先搜索(BFS)来解决这个问题,但需要额外记录每个节点的父节点和路径和。

关键点:

  • 使用队列进行层序遍历
  • 记录每个节点的父节点,以便回溯构建路径
  • 当找到满足条件的叶子节点时,通过父节点指针回溯构建路径

具体步骤:

  1. 如果根节点为空,返回空列表
  2. 初始化队列,将根节点入队
  3. 使用哈希表记录每个节点的父节点和从根节点到当前节点的路径和
  4. 当队列不为空时,执行以下操作: a. 出队一个节点 b. 如果当前节点是叶子节点且路径和等于目标和,通过父节点指针回溯构建路径并添加到结果中 c. 将当前节点的左右子节点入队,并更新它们的父节点和路径和
  5. 返回所有满足条件的路径

时间复杂度:O(n²),最坏情况下需要遍历所有节点,且每找到一条路径都需要O(n)的时间回溯构建路径 空间复杂度:O(n),队列和哈希表的空间

图解思路

方法一:DFS + 回溯过程分析表

以示例1为例,targetSum = 22

当前节点当前路径当前和是否叶子节点是否满足条件操作
5[5]5-继续递归
4[5,4]9-继续递归
11[5,4,11]20-继续递归
7[5,4,11,7]27回溯
2[5,4,11,2]22添加路径到结果,回溯
8[5,8]13-继续递归
13[5,8,13]26回溯
4[5,8,4]17-继续递归
5[5,8,4,5]22添加路径到结果,回溯
1[5,8,4,1]18回溯

方法二:路径回溯构建过程表

满足条件的叶子节点回溯路径构建过程最终路径
22 -> 11 -> 4 -> 5[5,4,11,2]
55 -> 4 -> 8 -> 5[5,8,4,5]

代码实现

C# 实现

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left;
 *     public TreeNode right;
 *     public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
public class Solution {
    // 方法一:深度优先搜索(DFS)+ 回溯
    public IList<IList<int>> PathSum(TreeNode root, int targetSum) {
        List<IList<int>> result = new List<IList<int>>();
        List<int> currentPath = new List<int>();
    
        // 辅助函数进行DFS
        DFS(root, targetSum, currentPath, result);
    
        return result;
    }
  
    private void DFS(TreeNode node, int targetSum, List<int> currentPath, List<IList<int>> result) {
        // 如果节点为空,直接返回
        if (node == null) {
            return;
        }
    
        // 将当前节点添加到路径中
        currentPath.Add(node.val);
    
        // 如果是叶子节点且路径和等于目标和,将当前路径添加到结果中
        if (node.left == null && node.right == null && targetSum == node.val) {
            // 注意需要创建一个新的列表,否则后续回溯会影响结果
            result.Add(new List<int>(currentPath));
        }
    
        // 递归遍历左右子树
        DFS(node.left, targetSum - node.val, currentPath, result);
        DFS(node.right, targetSum - node.val, currentPath, result);
    
        // 回溯,移除当前节点
        currentPath.RemoveAt(currentPath.Count - 1);
    }
  
    // 方法二:广度优先搜索(BFS)
    public IList<IList<int>> PathSumBFS(TreeNode root, int targetSum) {
        List<IList<int>> result = new List<IList<int>>();
    
        // 如果根节点为空,直接返回空列表
        if (root == null) {
            return result;
        }
    
        // 使用队列进行BFS
        Queue<TreeNode> nodeQueue = new Queue<TreeNode>();
        Queue<int> sumQueue = new Queue<int>();
        Dictionary<TreeNode, TreeNode> parentMap = new Dictionary<TreeNode, TreeNode>();
    
        nodeQueue.Enqueue(root);
        sumQueue.Enqueue(root.val);
        parentMap[root] = null;
    
        while (nodeQueue.Count > 0) {
            TreeNode node = nodeQueue.Dequeue();
            int currentSum = sumQueue.Dequeue();
        
            // 如果是叶子节点且路径和等于目标和
            if (node.left == null && node.right == null && currentSum == targetSum) {
                // 回溯构建路径
                List<int> path = new List<int>();
                TreeNode current = node;
            
                while (current != null) {
                    path.Add(current.val);
                    current = parentMap[current];
                }
            
                // 路径是从叶子节点到根节点的,需要反转
                path.Reverse();
                result.Add(path);
            }
        
            // 将左右子节点入队
            if (node.left != null) {
                nodeQueue.Enqueue(node.left);
                sumQueue.Enqueue(currentSum + node.left.val);
                parentMap[node.left] = node;
            }
        
            if (node.right != null) {
                nodeQueue.Enqueue(node.right);
                sumQueue.Enqueue(currentSum + node.right.val);
                parentMap[node.right] = node;
            }
        }
    
        return result;
    }
}

Python 实现

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    # 方法一:深度优先搜索(DFS)+ 回溯
    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        result = []
        current_path = []
    
        def dfs(node, target):
            # 如果节点为空,直接返回
            if not node:
                return
        
            # 将当前节点添加到路径中
            current_path.append(node.val)
        
            # 如果是叶子节点且路径和等于目标和,将当前路径添加到结果中
            if not node.left and not node.right and target == node.val:
                # 创建一个新的列表,避免后续回溯影响结果
                result.append(current_path[:])
        
            # 递归遍历左右子树
            dfs(node.left, target - node.val)
            dfs(node.right, target - node.val)
        
            # 回溯,移除当前节点
            current_path.pop()
    
        dfs(root, targetSum)
        return result
  
    # 方法二:广度优先搜索(BFS)
    def pathSumBFS(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        result = []
    
        # 如果根节点为空,直接返回空列表
        if not root:
            return result
    
        # 使用队列进行BFS
        node_queue = collections.deque([root])
        sum_queue = collections.deque([root.val])
        parent_map = {root: None}
        path_map = {root: [root.val]}
    
        while node_queue:
            node = node_queue.popleft()
            current_sum = sum_queue.popleft()
            current_path = path_map[node]
        
            # 如果是叶子节点且路径和等于目标和
            if not node.left and not node.right and current_sum == targetSum:
                result.append(current_path)
        
            # 将左右子节点入队
            if node.left:
                node_queue.append(node.left)
                sum_queue.append(current_sum + node.left.val)
                path_map[node.left] = current_path + [node.left.val]
                parent_map[node.left] = node
        
            if node.right:
                node_queue.append(node.right)
                sum_queue.append(current_sum + node.right.val)
                path_map[node.right] = current_path + [node.right.val]
                parent_map[node.right] = node
    
        return result

C++ 实现

/**
 * 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:
    // 方法一:深度优先搜索(DFS)+ 回溯
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        vector<vector<int>> result;
        vector<int> currentPath;
    
        dfs(root, targetSum, currentPath, result);
    
        return result;
    }
  
private:
    void dfs(TreeNode* node, int targetSum, vector<int>& currentPath, vector<vector<int>>& result) {
        // 如果节点为空,直接返回
        if (node == nullptr) {
            return;
        }
    
        // 将当前节点添加到路径中
        currentPath.push_back(node->val);
    
        // 如果是叶子节点且路径和等于目标和,将当前路径添加到结果中
        if (node->left == nullptr && node->right == nullptr && targetSum == node->val) {
            result.push_back(currentPath);
        }
    
        // 递归遍历左右子树
        dfs(node->left, targetSum - node->val, currentPath, result);
        dfs(node->right, targetSum - node->val, currentPath, result);
    
        // 回溯,移除当前节点
        currentPath.pop_back();
    }
  
public:
    // 方法二:广度优先搜索(BFS)
    vector<vector<int>> pathSumBFS(TreeNode* root, int targetSum) {
        vector<vector<int>> result;
    
        // 如果根节点为空,直接返回空列表
        if (root == nullptr) {
            return result;
        }
    
        // 使用队列进行BFS
        queue<TreeNode*> nodeQueue;
        queue<int> sumQueue;
        unordered_map<TreeNode*, TreeNode*> parentMap;
    
        nodeQueue.push(root);
        sumQueue.push(root->val);
        parentMap[root] = nullptr;
    
        while (!nodeQueue.empty()) {
            TreeNode* node = nodeQueue.front();
            nodeQueue.pop();
            int currentSum = sumQueue.front();
            sumQueue.pop();
        
            // 如果是叶子节点且路径和等于目标和
            if (node->left == nullptr && node->right == nullptr && currentSum == targetSum) {
                // 回溯构建路径
                vector<int> path;
                TreeNode* current = node;
            
                while (current != nullptr) {
                    path.push_back(current->val);
                    current = parentMap[current];
                }
            
                // 路径是从叶子节点到根节点的,需要反转
                reverse(path.begin(), path.end());
                result.push_back(path);
            }
        
            // 将左右子节点入队
            if (node->left != nullptr) {
                nodeQueue.push(node->left);
                sumQueue.push(currentSum + node->left->val);
                parentMap[node->left] = node;
            }
        
            if (node->right != nullptr) {
                nodeQueue.push(node->right);
                sumQueue.push(currentSum + node->right->val);
                parentMap[node->right] = node;
            }
        }
    
        return result;
    }
};

执行结果

C# 实现

  • 执行用时:128 ms
  • 内存消耗:42.1 MB

Python 实现

  • 执行用时:44 ms
  • 内存消耗:18.6 MB

C++ 实现

  • 执行用时:4 ms
  • 内存消耗:19.8 MB

性能对比

语言执行用时内存消耗特点
C#128 ms42.1 MB代码结构清晰,但性能较慢
Python44 ms18.6 MB代码简洁,性能适中
C++4 ms19.8 MB执行速度最快,内存占用适中

代码亮点

  1. 🎯 DFS + 回溯方法简洁高效,直接反映了问题的递归本质
  2. 💡 在回溯过程中正确处理路径的添加和移除,避免了路径混淆
  3. 🔍 BFS方法通过记录父节点信息,实现了路径的回溯构建
  4. 🎨 两种方法各有优势,DFS代码简洁,BFS避免了深度过大导致的栈溢出

常见错误分析

  1. 🚫 忘记在添加路径到结果时创建新的列表,导致后续回溯影响已添加的结果
  2. 🚫 没有正确处理叶子节点的定义,错误地将非叶子节点也计入结果
  3. 🚫 在BFS方法中忘记反转路径,导致路径顺序错误
  4. 🚫 没有正确更新目标和,导致路径判断错误

解法对比

解法时间复杂度空间复杂度优点缺点
DFS + 回溯O(n²)O(n)代码简洁,直观易懂对于高度较大的树可能导致栈溢出
BFSO(n²)O(n)避免栈溢出,适用于所有树实现复杂,需要额外空间存储父节点信息

相关题目