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);
    }
};
相关推荐
只是懒得想了5 小时前
C++实现密码破解工具:从MD5暴力破解到现代哈希安全实践
c++·算法·安全·哈希算法
m0_736919105 小时前
模板编译期图算法
开发语言·c++·算法
dyyx1115 小时前
基于C++的操作系统开发
开发语言·c++·算法
m0_736919106 小时前
C++安全编程指南
开发语言·c++·算法
蜡笔小马6 小时前
11.空间索引的艺术:Boost.Geometry R树实战解析
算法·r-tree
-Try hard-6 小时前
数据结构:链表常见的操作方法!!
数据结构·算法·链表·vim
2301_790300966 小时前
C++符号混淆技术
开发语言·c++·算法
我是咸鱼不闲呀6 小时前
力扣Hot100系列16(Java)——[堆]总结()
java·算法·leetcode
嵌入小生0076 小时前
单向链表的常用操作方法---嵌入式入门---Linux
linux·开发语言·数据结构·算法·链表·嵌入式
LabVIEW开发6 小时前
LabVIEW金属圆盘压缩特性仿真
算法·labview·labview知识·labview功能·labview程序