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);
    }
};
相关推荐
ValhallaCoder3 小时前
hot100-栈
数据结构·python·算法·
WW_千谷山4_sch7 小时前
洛谷B3688:[语言月赛202212]旋转排列(新解法:deque双端队列)
数据结构·c++·算法
Zachery Pole7 小时前
【代码随想录】二叉树
算法
漂流瓶jz7 小时前
UVA-11214 守卫棋盘 题解答案代码 算法竞赛入门经典第二版
c++·算法·dfs·aoapc·算法竞赛入门经典·迭代加深搜索·八皇后
浮生09198 小时前
DHUOJ 基础 88 89 90
算法
v_for_van8 小时前
力扣刷题记录7(无算法背景,纯C语言)
c语言·算法·leetcode
先做个垃圾出来………8 小时前
3640. 三段式数组 II
数据结构·算法
tankeven10 小时前
HJ93 数组分组
c++·算法
Σίσυφος190010 小时前
LM 在 PnP(EPnP / P3P)的应用
算法
陈天伟教授10 小时前
人工智能应用- 人工智能交叉:01. 破解蛋白质结构之谜
人工智能·神经网络·算法·机器学习·推荐算法