LeetCode:路径总和 III

文章目录

  • [1. 题目简介](#1. 题目简介)
  • [2. 题目解析](#2. 题目解析)
    • [2.1 暴力枚举 dfs](#2.1 暴力枚举 dfs)
    • [2.2 前缀和优化](#2.2 前缀和优化)

1. 题目简介


LEETCODE 链接

2. 题目解析

2.1 暴力枚举 dfs

这道题目需要统计所有等于targetNum的路径总数,题中的路径要求是灵活的,不需要从根结点开始,无需在叶子结点结束,方向则是要求严格向下,从父结点到子结点。

刚开始拿到此题时,很容易想到一种暴力做法:走先序遍历逻辑,这样天然支持从根结点开始向下遍历所有结点,再使用一个变量动态记录路径和,每个结点选或不选,两种情况。 这种做法是不行的,因为会出现互相间隔的两个结点同时选中,而中间结点不选,这样的路径是非法的。

上述方法,如果每个结点都选,那么此时路径便是严格从根结点开始,不满足题目路径要求,实质这样会少统计,因为统计了所有严格从根结点开始的路径,却没有统计从其它结点开始的路径。

因此,在上述分析下,我们自然想到一种做法:从某个结点开始的所有路径可以通过遍历,轻松枚举,那么只需要对整棵树中的每个结点都暴力统计所有路径,那么自然就穷尽了所有符合题目要求的路径。

上述对于一个结点开始路径的dfs枚举,使用前序遍历,因为前序遍历顺序为根左右 ,这样到达的根结点最快结算路径的延伸和枚举,也更直观,更符合直觉。

cpp 复制代码
/**
 * 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:
    int dfs(TreeNode* root, long long targetSum) {
        if(!root)
            return 0;
        int ret = 0;
        targetSum -= root->val;
        if(targetSum == 0)
            ret++;
        ret += dfs(root->left, targetSum);
        ret += dfs(root->right, targetSum);
        return ret;
    }
    int pathSum(TreeNode* root, int targetSum) {
        if(!root)
            return 0;
        int ret = dfs(root, targetSum);
        ret += pathSum(root->left, targetSum);
        ret += pathSum(root->right, targetSum);
        return ret;
    }
};

上述算法时间复杂度在 O(n ^ 2), 空间复杂度在 O(n),因为每个结点都需要暴力枚举所有路径,而每个结点枚举中,最坏可能穷尽结点数接近 n 个结点(非平衡二叉树,二叉树退化为链表情况),最坏空间复杂度同样在二叉树退化为链表时达到。

2.2 前缀和优化

在上述暴力枚举每个结点过程中,存在大量重复计算,因为父结点中路径和的一部分,本质就是其子结点或孙子结点的路径和,而这些路径和在暴力枚举中,被大量重复计算了,那么能否优化这些重复计算呢?

虽然此题是树形结构 ,但是仔细审题,发现路径严格要求自上而下,严格从父结点到子结点,同时路径的起始位置无严格要求这道题就与"和为K的子数组",本质相同,都可以通过 哈希 + 前缀和 方式,将 N^2 的复杂度优化到 N。

那么,根据上述思路,我们得到 dfs 本质要做什么:统计整颗二叉树中,以其中每个结点作为结束结点合法路径总数,并将合法路径树返回。而在递归中,仅需处理当前情况:维护到当前结点的前缀和,根据哈希获得以当前结点为结尾的合法路径数,维护前缀和哈希结构,然后再分别递归左右子树即可。

cpp 复制代码
/**
 * 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:
    unordered_map<long long,int> prefix;
    int dfs(TreeNode* root, long long sum, int targetSum) {
        if(root == nullptr)
            return 0;
        sum += root->val;
        int ret = prefix[sum - targetSum];
        
        prefix[sum]++; // 维护前缀和哈希结构
        ret += dfs(root->left, sum, targetSum);
        ret += dfs(root->right, sum, targetSum);
        prefix[sum]--; // 需要理解这一步回溯,关键所在
        
        return ret;
    }
    int pathSum(TreeNode* root, int targetSum) {
        prefix.clear();
        prefix[0] = 1; // 关键的初始化
        return dfs(root, 0, targetSum);
    }
};
相关推荐
NGC_66111 分钟前
八大排序对比及实现
数据结构·算法·排序算法
进击的小头1 分钟前
第7篇:动态规划的数值求解算法
python·算法·动态规划
FMRbpm12 分钟前
斑马日记2026.3.13
数据结构·算法
NGC_66111 小时前
ArrayList扩容机制
java·前端·算法
xsyaaaan5 小时前
leetcode-hot100-双指针:283移动零-11盛最多水的容器-15三数之和-42接雨水
算法·leetcode
炽烈小老头8 小时前
【每天学习一点算法 2026/03/08】相交链表
学习·算法·链表
一碗白开水一9 小时前
【工具相关】OpenClaw 配置使用飞书:打造智能飞书助手全流程指南(亲测有效,放心享用)
人工智能·深度学习·算法·飞书
仰泳的熊猫9 小时前
题目2194:蓝桥杯2018年第九届真题-递增三元组
数据结构·c++·算法
Tisfy10 小时前
LeetCode 1888.使二进制字符串字符交替的最少反转次数:前缀和O(1)
算法·leetcode·字符串·题解
滴滴答滴答答10 小时前
机考刷题之 9 LeetCode 503 下一个更大元素 II
算法·leetcode·职场和发展