LeetCode 112. 路径总和 - 避坑指南与多语言全解法(C/C++/Python)

前言

在刷 LeetCode 二叉树相关的题目时,"路径总和"是一道非常经典的必刷题。很多初学者在第一次尝试递归时,思路是对的,但往往会因为"节点值扣除错误"而掉进死胡同(比如测试用例 [1,2] 莫名其妙输出 true)。今天这篇博客,我们就来复盘一下这个常见错误,并一次性给出 C、C++ 和 Python 的递归与非递归(迭代)全套模板,直接帮你把这道题彻底吃透!


一、 常见踩坑记录:为什么 [1,2] 会输出 true

在实现向下递归减去节点值时,很容易写出类似下面这样的逻辑:

错误思路 :在父节点时计算 targetSum - 父节点的值,向左/右子树递归时,再次 传入 targetSum - 父节点的值

这样会导致什么问题呢?

当输入树为 root = [1, 2](根为 1,左子节点为 2),且 targetSum = 2 时:

  1. 根节点 1 扣除自己的值:2 - 1 = 1

  2. 进入左子树 2,却再次减去了父节点 1 的值1 - 1 = 0

  3. 此时到了叶子节点 2,程序一看剩余和为 0,直接返回 true

    本质原因:叶子节点的值(2)根本没有参与计算,导致了严重的误判!

正确的解题核心"走到哪里,就算到哪里" 。要么在传参时减去子节点 的值,要么让每个节点只负责处理当前这层的值。


二、 优雅的递归法 (DFS)

最推荐的写法是不需要辅助函数,直接利用原函数。每次递归将 targetSum 减去当前节点的值,直到叶子节点判断剩余的值是否与叶子节点的值相等。

1. C / C++ 递归版本

cpp 复制代码
// C 和 C++ 的代码在递归法下完全一致
bool hasPathSum(struct TreeNode* root, int targetSum) {
    // 1. 如果节点为空,说明路径不通
    if (!root) return false;

    // 2. 如果是叶子节点,判断当前剩余的 targetSum 是否等于自己的值
    if (!root->left && !root->right) {
        return targetSum == root->val;
    }

    // 3. 不是叶子节点,向左右子树继续寻找
    return hasPathSum(root->left, targetSum - root->val) || 
           hasPathSum(root->right, targetSum - root->val);
}

2. Python 递归版本

python 复制代码
class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:
            return False
        if not root.left and not root.right:
            return targetSum == root.val
        
        return self.hasPathSum(root.left, targetSum - root.val) or \
               self.hasPathSum(root.right, targetSum - root.val)

三、 进阶:非递归 / 迭代法

在实际工程中,如果树的深度非常大,递归可能会导致栈溢出。掌握非递归(迭代法)是非常有必要的。迭代法的核心是利用栈(Stack)来模拟递归过程。栈中需要同时保存"当前节点"和"从根到当前节点的路径和"。

1. C 语言迭代版

由于纯 C 语言没有标准库的 Stack,我们需要用数组来模拟。通常 LeetCode 的树节点最多几千个,开一个万级数组足够了。

objectivec 复制代码
bool hasPathSum(struct TreeNode* root, int targetSum) {
    if (!root) return false;

    // 使用两个数组模拟栈:一个存节点,一个存当前路径和
    struct TreeNode* nodeStack[10000];
    int sumStack[10000];
    int top = -1; // 栈顶指针

    // 根节点入栈
    nodeStack[++top] = root;
    sumStack[top] = root->val;

    while (top >= 0) {
        // 出栈
        struct TreeNode* node = nodeStack[top];
        int currentSum = sumStack[top--];

        // 如果是叶子节点,且路径和等于 targetSum,返回 true
        if (!node->left && !node->right && currentSum == targetSum) {
            return true;
        }

        // 右子节点入栈(注意:栈是后进先出,先压右再压左,出栈时就是先左后右)
        if (node->right) {
            nodeStack[++top] = node->right;
            sumStack[top] = currentSum + node->right->val;
        }
        // 左子节点入栈
        if (node->left) {
            nodeStack[++top] = node->left;
            sumStack[top] = currentSum + node->left->val;
        }
    }
    return false;
}

2. C++ 迭代版

C++ 有了 STL 就方便很多了,我们可以用 std::stack 配合 std::pair 来存储节点和当前和。

cpp 复制代码
#include <stack>
#include <utility>

class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (!root) return false;
        
        std::stack<std::pair<TreeNode*, int>> st;
        st.push({root, root->val});
        
        while (!st.empty()) {
            auto [node, currentSum] = st.top();
            st.pop();
            
            // 到达叶子节点并验证总和
            if (!node->left && !node->right && currentSum == targetSum) {
                return true;
            }
            
            // 压入左右子节点(先右后左)
            if (node->right) {
                st.push({node->right, currentSum + node->right->val});
            }
            if (node->left) {
                st.push({node->left, currentSum + node->left->val});
            }
        }
        return false;
    }
};

3. Python 迭代版

Python 中直接使用 list 就可以完美模拟栈的操作(append 压栈,pop 出栈)。

python 复制代码
class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:
            return False
            
        # 栈里存储元组:(节点, 到达该节点时的路径和)
        stack = [(root, root.val)]
        
        while stack:
            node, current_sum = stack.pop()
            
            if not node.left and not node.right and current_sum == targetSum:
                return True
                
            if node.right:
                stack.append((node.right, current_sum + node.right.val))
            if node.left:
                stack.append((node.left, current_sum + node.left.val))
                
        return False

四、 复杂度分析

无论是递归还是迭代,本质都是深度优先搜索 (DFS):

  • 时间复杂度:O(N),其中 N 是二叉树的节点数。每个节点最多被访问一次。

  • 空间复杂度:O(H),其中 H 是二叉树的高度。在最坏情况下(树退化为链表),空间复杂度为 O(N);在最好情况(平衡二叉树)下,空间复杂度为 O(log N)。

总结:解决树的问题时,边界条件(是否为空)和递归终止条件(是否为叶子节点)是最关键的。理清这两点,代码自然水到渠成!

最后照例贴上卡哥的代码随想录

112. 路径总和 | 深度优先遍历 | 回溯 | 代码随想录

相关推荐
计算机安禾4 分钟前
【数据结构与算法】第44篇:堆(Heap)的实现
c语言·开发语言·数据结构·c++·算法·排序算法·图论
tankeven10 分钟前
HJ175 小红的整数配对
c++·算法
ShineWinsu19 分钟前
对于Linux:“一切皆文件“以及缓冲区的解析
linux·运维·c++·面试·笔试·缓冲区·一切皆文件
wfbcg29 分钟前
每日算法练习:LeetCode 36. 有效的数独 ✅
算法·leetcode·职场和发展
沈跃泉39 分钟前
C++串口类实现
c++·windows·串口通信·串口类
jolimark1 小时前
C语言标准与编译器,新手该看哪些?
c语言·开发工具·环境搭建·编译器·新手指南
智者知已应修善业1 小时前
【51单片机非精准计时2个外部中断启停】2023-5-29
c++·经验分享·笔记·算法·51单片机
沐雪轻挽萤1 小时前
3. C++17新特性-带初始化的 if 和 switch 语句
开发语言·c++
Magic--2 小时前
C++ 智能指针
开发语言·c++·算法
呱呱巨基2 小时前
网络基础概念
linux·网络·c++·笔记·学习