【每日算法】LeetCode 437. 路径总和 III

对前端开发者而言,学习算法绝非为了"炫技"。它是你从"页面构建者"迈向"复杂系统设计者"的关键阶梯。它将你的编码能力从"实现功能"提升到"设计优雅、高效解决方案"的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎

LeetCode 437. 路径总和 III

1. 题目描述

给定一个二叉树的根节点 root 和一个整数 targetSum,求该二叉树里节点值之和等于 targetSum路径 的数目。

路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

示例 1:

复制代码
输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有:
1. 5 → 3
2. 5 → 2 → 1
3. -3 → 11

示例 2:

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

2. 问题分析

2.1 问题特点

  1. 路径定义灵活:路径可以从任意节点开始,到任意节点结束
  2. 方向限制:只能从上到下(父节点到子节点)
  3. 数值特点:节点值可能为负数,这增加了问题的复杂性
  4. 树结构:典型的二叉树遍历问题

2.2 前端视角理解

在前端开发中,类似的问题场景包括:

  • DOM树遍历:查找符合特定条件的节点组合
  • 组件树状态管理:统计组件树中满足特定状态条件的路径
  • 路由权限验证:检查权限链是否符合特定规则

3. 解题思路

3.1 暴力递归法(DFS)

遍历每个节点,以该节点为起点向下搜索所有路径,检查路径和是否等于targetSum。

3.2 前缀和优化法(最优解)

利用前缀和思想,将路径和问题转化为"两数之差"问题,类似数组中的两数之和问题。

核心思想

  1. 记录从根节点到当前节点的路径和(前缀和)
  2. 查找当前前缀和与targetSum的差值是否在历史前缀和中出现过
  3. 使用哈希表(Map)存储前缀和的出现次数

4. 代码实现

4.1 方法一:双重递归(暴力法)

javascript 复制代码
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */

/**
 * 暴力递归法
 * 时间复杂度:O(n²),空间复杂度:O(h)
 */
var pathSum = function(root, targetSum) {
    if (!root) return 0;
    
    // 从当前节点开始的路径
    const countFromNode = (node, sum) => {
        if (!node) return 0;
        
        let count = 0;
        // 如果当前节点的值等于剩余和,找到一条路径
        if (node.val === sum) {
            count++;
        }
        
        // 继续向左右子树搜索
        count += countFromNode(node.left, sum - node.val);
        count += countFromNode(node.right, sum - node.val);
        
        return count;
    };
    
    // 从当前节点开始的路径 + 从左子树开始的路径 + 从右子树开始的路径
    return countFromNode(root, targetSum) +
           pathSum(root.left, targetSum) +
           pathSum(root.right, targetSum);
};

4.2 方法二:前缀和优化法(最优解)

javascript 复制代码
/**
 * 前缀和优化法 - 最优解
 * 时间复杂度:O(n),空间复杂度:O(n)
 */
var pathSum = function(root, targetSum) {
    // 使用Map存储前缀和及其出现次数
    const prefixSumMap = new Map();
    // 初始化:前缀和为0的路径有1条(空路径)
    prefixSumMap.set(0, 1);
    
    /**
     * DFS遍历
     * @param {TreeNode} node 当前节点
     * @param {number} currentSum 当前路径和
     * @return {number} 符合条件的路径数
     */
    const dfs = (node, currentSum) => {
        if (!node) return 0;
        
        // 更新当前路径和
        currentSum += node.val;
        
        // 查找是否存在前缀和使得 currentSum - prefixSum = targetSum
        // 即:prefixSum = currentSum - targetSum
        const target = currentSum - targetSum;
        let count = prefixSumMap.get(target) || 0;
        
        // 将当前路径和加入哈希表
        prefixSumMap.set(currentSum, (prefixSumMap.get(currentSum) || 0) + 1);
        
        // 递归遍历左右子树
        count += dfs(node.left, currentSum);
        count += dfs(node.right, currentSum);
        
        // 回溯:移除当前节点的路径和(因为要返回上一层)
        prefixSumMap.set(currentSum, prefixSumMap.get(currentSum) - 1);
        if (prefixSumMap.get(currentSum) === 0) {
            prefixSumMap.delete(currentSum);
        }
        
        return count;
    };
    
    return dfs(root, 0);
};

4.3 方法三:迭代法(BFS + DFS)

javascript 复制代码
/**
 * BFS + DFS组合方法
 * 时间复杂度:O(n²),空间复杂度:O(n)
 */
var pathSum = function(root, targetSum) {
    if (!root) return 0;
    
    let totalCount = 0;
    const queue = [root];
    
    // BFS遍历每个节点作为起始点
    while (queue.length) {
        const node = queue.shift();
        
        // DFS搜索以当前节点为起点的所有路径
        const dfs = (currentNode, currentSum) => {
            if (!currentNode) return;
            
            currentSum += currentNode.val;
            if (currentSum === targetSum) {
                totalCount++;
            }
            
            dfs(currentNode.left, currentSum);
            dfs(currentNode.right, currentSum);
        };
        
        dfs(node, 0);
        
        // 将子节点加入队列
        if (node.left) queue.push(node.left);
        if (node.right) queue.push(node.right);
    }
    
    return totalCount;
};

5. 复杂度与优缺点对比

方法 时间复杂度 空间复杂度 优点 缺点 适用场景
暴力递归法 O(n²) O(h) 思路直观,实现简单 效率低,重复计算多 小规模树,学习理解
前缀和优化法 O(n) O(n) 效率最高,最优解 需要理解前缀和概念 大规模树,生产环境
BFS+DFS组合 O(n²) O(n) 结合两种遍历方式 效率不高,空间占用大 特定场景,需要层次信息

6. 总结与前端应用场景

6.1 核心要点总结

  1. 前缀和技巧是解决此类路径和问题的关键,将树路径问题转化为数组问题
  2. 回溯思想在树遍历中至关重要,确保状态正确恢复
  3. 哈希表优化是提升算法效率的常用手段

6.2 前端实际应用场景

  1. DOM事件委托优化

    javascript 复制代码
    // 类似思想:事件委托中使用Map存储事件处理器
    class EventDelegator {
        constructor() {
            this.handlers = new Map();
        }
        
        addHandler(path, handler) {
            // 存储路径与处理器的映射
            this.handlers.set(path, handler);
        }
    }
  2. 路由权限验证

    javascript 复制代码
    // 检查用户是否有访问某个嵌套路由的权限
    function checkRoutePermission(routes, requiredPermissions) {
        const permissionMap = new Map();
        // 使用类似前缀和的思想累积权限
    }
  3. 组件状态管理

    javascript 复制代码
    // 在React/Vue组件树中统计特定状态
    function countStatePaths(componentTree, targetState) {
        // 使用前缀和思想查找状态组合
    }
  4. 性能监控路径分析

    javascript 复制代码
    // 分析页面加载过程中资源加载路径
    class PerformanceAnalyzer {
        analyzeCriticalPath(resources, targetLoadTime) {
            // 类似路径和问题的变体
        }
    }
相关推荐
安_4 小时前
为什么 Vue 要用 npm run dev 启动
前端·vue.js·npm
六便士的理想4 小时前
el-table实现滑窗列
前端·vue.js
阿蓝灬4 小时前
Chrome Lighthouse优化
前端·chrome
宵时待雨5 小时前
C语言笔记归纳20:文件操作
c语言·开发语言·笔记·算法
程序员爱钓鱼6 小时前
Node.js 编程实战:图像与文件上传下载
前端·后端·node.js
程序员爱钓鱼6 小时前
Node.js 编程实战:日志管理与分析
后端·面试·node.js
kong79069287 小时前
环境搭建-运行前端工程(vue)
前端·前端环境
谷歌开发者7 小时前
Web 开发指向标|开发者工具 AI 辅助功能的 5 大实践应用
前端·人工智能
alphaTao7 小时前
LeetCode 每日一题 2025/12/15-2025/12/21
算法·leetcode