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. 路径总和 | 深度优先遍历 | 回溯 | 代码随想录

相关推荐
干啥啥不行,秃头第一名1 小时前
C++中的观察者模式
开发语言·c++·算法
阿Y加油吧1 小时前
力扣打卡——反转链表、回文链表判断 题解
算法·leetcode
羊小猪~~1 小时前
算法/力扣--数组典型题目
c语言·c++·python·算法·leetcode·职场和发展·求职招聘
老师用之于民2 小时前
【DAY29】DS18B20 传感器特性、时序协议及 51 单片机驱动开发
c语言·驱动开发·单片机·嵌入式硬件
逻辑君2 小时前
Research in Brain-inspired Computing [1]-弹球游戏
c++·人工智能·神经网络·机器学习
x_xbx2 小时前
LeetCode:198. 打家劫舍
算法·leetcode·职场和发展
_日拱一卒2 小时前
LeetCode:盛最多水的容器
数据结构·算法·leetcode
ulias2122 小时前
C++ 异常处理机制
java·开发语言·c++
计算机安禾2 小时前
【数据结构与算法】第2篇:C语言核心机制回顾(一):指针、数组与结构体
c语言·开发语言·数据结构·c++·算法·链表·visual studio