当前位置: 首页 > news >正文

深入剖析二叉树路径和问题:从暴力法到前缀和优化(C++实现)

力扣437. 路径总和 III

问题描述

给定一个二叉树的根节点 root 和一个整数 targetSum,要求找出二叉树中所有路径,这些路径上的节点值之和等于 targetSum。路径的定义是从父节点指向子节点的方向(向下),且不需要从根节点开始,也不需要在叶子节点结束。

示例分析

示例1:

// 输入:
//     10
//    /  \
//   5   -3
//  / \    \
// 3   2   11
// ...
// targetSum = 8
// 预期输出:3

示例2:

// 输入:
// [5,4,8,11,null,13,4,7,2,null,null,5,1]
// targetSum = 22
// 预期输出:3

C++解题实现

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

class Solution {
public:
    int pathSum(TreeNode* root, int targetSum) {
        if (!root) return 0;
        
        // 计算以当前节点为起点的路径数
        int count = dfs(root, targetSum);
        
        // 递归计算左右子树的路径数
        count += pathSum(root->left, targetSum);
        count += pathSum(root->right, targetSum);
        
        return count;
    }
    
private:
    int dfs(TreeNode* node, long long sum) {
        if (!node) return 0;
        
        int count = 0;
        sum -= node->val;
        if (sum == 0) {
            count++;
        }
        
        count += dfs(node->left, sum);
        count += dfs(node->right, sum);
        
        return count;
    }
};

复杂度分析:

  • 时间复杂度:O(n²)

  • 空间复杂度:O(n)

方法二:前缀和优化(哈希表+DFS)

/**
 * 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 {
    unordered_map<long long,int> cnt; // 前缀和哈希表,键为long long防止溢出
    int res=0; // 结果计数器
    
    void dfs(TreeNode* curnode, long long sum, int targetSum) {
        if(!curnode) return;
        
        // 更新当前路径和
        sum += curnode->val;
        
        // 检查是否存在满足条件的前缀和
        if(cnt.find(sum-targetSum) != cnt.end()) 
            res += cnt[sum-targetSum];
        
        // 记录当前前缀和
        cnt[sum]++;
        
        // 递归处理左右子树
        if(curnode->left) dfs(curnode->left, sum, targetSum);
        if(curnode->right) dfs(curnode->right, sum, targetSum);
        
        // 回溯,恢复哈希表状态
        cnt[sum]--;
    }

public:
    int pathSum(TreeNode* root, int targetSum) {
        cnt[0] = 1; // 初始化空路径和
        dfs(root, 0, targetSum);
        return res;
    }
};

时间复杂度分析

  • 时间复杂度:O(n),其中n是二叉树的节点数

  • 分析:每个节点仅被访问一次,在访问每个节点时,哈希表的查找、插入和删除操作都是平均O(1)时间复杂度

空间复杂度分析

  • 空间复杂度:O(n)

  • 分析

    • 递归调用栈的深度在最坏情况下为O(n)(当树退化为链表时)

    • 哈希表cnt在最坏情况下需要存储O(n)个不同的前缀和

关键点详解

  1. 前缀和哈希表设计

    • 使用unordered_map<long long, int>存储前缀和及其出现次数

    • long long类型确保大数计算不会溢出(节点值可达1e9,多个节点累加可能超过int范围)

    • 哈希表记录从根节点到当前节点的路径和出现的次数

  2. 核心算法逻辑

    • 初始化cnt[0]=1处理从根节点开始的路径

    • 路径和更新sum += curnode->val计算当前路径和

    • 结果统计:查找sum-targetSum是否存在哈希表中

    • 状态维护:递归前后正确更新哈希表

  3. 回溯机制

    • 在递归返回时执行cnt[sum]--恢复状态

    • 确保不同分支的路径计算不会相互干扰

    • 维护哈希表仅包含当前路径的前缀和信息

边界情况处理

  1. 空树处理

    if(!curnode) return;

    直接返回,不影响结果计数

  2. 大数处理

    long long sum; // 使用64位整数存储路径和

    防止节点值累加溢出

  3. 负数和零处理

    • 算法天然支持节点值为负数的情况

    • targetSum为0的情况也能正确处理

常见问题解答

Q1:为什么哈希表要使用long long而不是int?

A:节点值范围是[-1e9,1e9],当多个节点值累加时可能超出int范围(约±2.1e9)。例如,三个1e9的节点相加就是3e9,超过了int最大值。使用long long(范围约±9e18)可以避免溢出问题。

Q2:为什么要初始化cnt[0]=1?

A:这表示存在一个空路径的和为0。这样当从根节点到某节点的路径和正好等于targetSum时,sum-targetSum=0能在哈希表中找到匹配。例如,如果从根节点到当前节点的和正好是targetSum,我们需要能够统计这种情况。

Q3:为什么要在递归返回时执行cnt[sum]--?

A:这是回溯的关键步骤。当递归返回到父节点时,当前节点的路径和不应该再被其他分支的路径计算所使用。通过减少计数,我们确保哈希表只包含当前路径上的前缀和。

Q4:如何处理从任意节点开始的路径?

A:通过维护从根节点到当前节点的路径和sum,并检查sum-targetSum是否存在,我们实际上检查了所有以当前节点为终点的路径。哈希表记录的前缀和对应着路径的起点可能在树的任何位置。

算法正确性证明

  1. 完整性:算法遍历了树的所有节点,考虑了所有可能的路径

  2. 正确性

    • sum-targetSum存在于哈希表中时,说明存在一个前缀和,使得这两个节点之间的路径和等于targetSum

    • 回溯机制确保每个路径的计算都是独立的

  3. 终止性:递归必然终止,因为树是有限大小的

性能优化建议

  1. 迭代实现:可以用显式栈实现DFS,避免递归栈溢出

  2. 哈希表清理:对于特别深的树,可以定期清理哈希表中不必要的前缀和

  3. 并行计算:对于大型树,可以考虑并行处理不同子树

扩展应用

这种前缀和+哈希表的方法还可用于解决:

  1. 二叉搜索树中特定和的路径查找

  2. 二叉树中最大路径和问题

  3. 其他树形结构的路径统计问题

  4. 图形结构中类似的前缀和问题

respect!

相关文章:

  • 自然语言处理(29:(终章Attention 5.)Attention的应用)(完结)
  • HTML实现图片上添加水印的工具
  • Spring 相关知识点
  • DirectX修复工具(DirectX Repair)官网免费下载
  • 科研论文word格式参考文献自动编码脚本
  • 力扣HOT100之链表:141. 环形链表
  • 数据结构4
  • 论文笔记:Instruction-Tuning Llama-3-8B Excels in City-Scale MobilityPrediction
  • vuex和pinia区别
  • 16 网络服务应用
  • 软考中级-软件设计师 2023年上半年上午题真题解析:通关秘籍+避坑指南
  • 在 Ubuntu 22.04 上安装 Docker Compose 的步骤
  • 帕累托分布的均值和方差
  • 【第一节】Python爬虫基础-HTTP基本原理
  • 基于 Hough 变换的直线检测
  • 高性能计算面经
  • Docker学习之容器虚拟化与虚拟机的区别(day11)
  • python笔记:unsloth (Basic)
  • Python项目-基于Flask的个人博客系统设计与实现(2)
  • 【系统移植】(一)概念流程
  • 学seo可以做网站吗/网站推广的几种方法
  • 做网站淮南/自建网站平台有哪些
  • 周到的做网站/seo排名工具给您好的建议
  • 网站 如何做 同时在线/网站建站系统
  • 网站平台专题如何制作/自己建网站怎么弄