热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

bool类型返回值判断_二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?...

就看是否需要遍历整个二叉树112.路径总和给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。说明

就看是否需要遍历整个二叉树

112. 路径总和

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

说明: 叶子节点是指没有子节点的节点。

示例: 给定如下二叉树,以及目标和 sum = 22,

37d4f2c42fd507ac0fbfeacc265ae277.png

返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。

思路

这道题我们要遍历从根节点到叶子节点的的路径看看总和是不是目标和。

递归

可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树

  1. 确定递归函数的参数和返回类型

参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。

「再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?」

在文章二叉树:我的左下角的值是多少?中,我给出了一个结论:

「如果需要搜索整颗二叉树,那么递归函数就不要返回值,如果要搜索其中一条符合条件的路径,递归函数就需要返回值,因为遇到符合条件的路径了就要及时返回。」

在二叉树:我的左下角的值是多少? 中,因为要遍历树的所有路径,找出深度最深的叶子节点,所以递归函数不要返回值。

而本题我们要找一条符合条件的路径,所以递归函数需要返回值,及时返回,那么返回类型是什么呢?

如图所示:

f35e00646fd2bc46a18f106361f9deb5.png

图中可以看出,遍历的路线,并不要遍历整棵树,所以递归函数需要返回值,可以用bool类型表示。

所以代码如下:

bool traversal(TreeNode* cur, int count)   // 注意函数的返回类型

  1. 确定终止条件

首先计数器如何统计这一条路径的和呢?

不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。

如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。

如果遍历到了叶子节点,count不为0,就是没找到。

递归终止条件代码如下:

if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0if (!cur->left && !cur->right) return false; // 遇到叶子节点而没有找到合适的边,直接返回

  1. 确定单层递归的逻辑

因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。

递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。

代码如下:

if (cur->left) { // 左 (空节点不遍历)    // 遇到叶子节点返回true,则直接返回true    if (traversal(cur->left, count - cur->left->val)) return true; // 注意这里有回溯的逻辑}if (cur->right) { // 右 (空节点不遍历)    // 遇到叶子节点返回true,则直接返回true    if (traversal(cur->right, count - cur->right->val)) return true; // 注意这里有回溯的逻辑}return false;

以上代码中是包含着回溯的,没有回溯,如何后撤重新找另一条路径呢。

回溯隐藏在traversal(cur->left, count - cur->left->val)这里, 因为把count - cur->left->val 直接作为参数传进去,函数结束,count的数值没有改变。

为了把回溯的过程体现出来,可以改为如下代码:

if (cur->left) { // 左    count -= cur->left->val; // 递归,处理节点;    if (traversal(cur->left, count)) return true;    count += cur->left->val; // 回溯,撤销处理结果}if (cur->right) { // 右     count -= cur->right->val;    if (traversal(cur->right, count - cur->right->val)) return true;     count += cur->right->val;}return false;

整体代码如下:

class Solution {private:    bool traversal(TreeNode* cur, int count) {        if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0        if (!cur->left && !cur->right) return false; // 遇到叶子节点直接返回        if (cur->left) { // 左            count -= cur->left->val; // 递归,处理节点;            if (traversal(cur->left, count)) return true;            count += cur->left->val; // 回溯,撤销处理结果        }        if (cur->right) { // 右            count -= cur->right->val; // 递归,处理节点;            if (traversal(cur->right, count)) return true;            count += cur->right->val; // 回溯,撤销处理结果        }        return false;    }public:    bool hasPathSum(TreeNode* root, int sum) {        if (root == NULL) return false;        return traversal(root, sum - root->val);    }};

以上代码精简之后如下:

class Solution {public:    bool hasPathSum(TreeNode* root, int sum) {        if (root == NULL) return false;        if (!root->left && !root->right && sum == root->val) {            return true;        }        return hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val);    }};

「是不是发现精简之后的代码,已经完全看不出分析的过程了,所以我们要把题目分析清楚之后,在追求代码精简。」 这一点我已经强调很多次了!

迭代

如果使用栈模拟递归的话,那么如果做回溯呢?

「此时栈里一个元素不仅要记录该节点指针,还要记录从头结点到该节点的路径数值总和。」

C++就我们用pair结构来存放这个栈里的元素。

定义为:pair pair

这个为栈里的一个元素。

如下代码是使用栈模拟的前序遍历,如下:(详细注释)

class Solution {public:    bool hasPathSum(TreeNode* root, int sum) {        if (root == NULL) return false;        // 此时栈里要放的是pair        stack> st;        st.push(pair(root, root->val));        while (!st.empty()) {            pair node = st.top();            st.pop();            // 如果该节点是叶子节点了,同时该节点的路径数值等于sum,那么就返回true            if (!node.first->left && !node.first->right && sum == node.second) return true;            // 右节点,压进去一个节点的时候,将该节点的路径数值也记录下来            if (node.first->right) {                st.push(pair(node.first->right, node.second + node.first->right->val));            }            // 左节点,压进去一个节点的时候,将该节点的路径数值也记录下来            if (node.first->left) {                st.push(pair(node.first->left, node.second + node.first->left->val));            }        }        return false;    }};

如果大家完全理解了本地的递归方法之后,就可以顺便把leetcode上113. 路径总和II做了。

113. 路径总和II

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

说明: 叶子节点是指没有子节点的节点。

示例: 给定如下二叉树,以及目标和 sum = 22,

5ea8dd35b284b90e425d9f2b9c99388c.png

思路

113.路径总和II要遍历整个树,找到所有路径,「所以递归函数不要返回值!」

如图:

77ddf0782e0a935e023203595d6db7dd.png

为了尽可能的把细节体现出来,我写出如下代码(「这份代码并不简洁,但是逻辑非常清晰」)

class Solution {private:    vector> result;    vector path;    // 递归函数不需要返回值,因为我们要遍历整个树    void traversal(TreeNode* cur, int count) {        if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点切找到了和为sum的路径            result.push_back(path);            return;        }        if (!cur->left && !cur->right) return ; // 遇到叶子节点而没有找到合适的边,直接返回        if (cur->left) { // 左 (空节点不遍历)            path.push_back(cur->left->val);            count -= cur->left->val;            traversal(cur->left, count);    // 递归            count += cur->left->val;        // 回溯            path.pop_back();                // 回溯        }        if (cur->right) { // 右 (空节点不遍历)            path.push_back(cur->right->val);            count -= cur->right->val;            traversal(cur->right, count);   // 递归            count += cur->right->val;       // 回溯            path.pop_back();                // 回溯        }        return ;    }public:    vector> pathSum(TreeNode* root, int sum) {        result.clear();        path.clear();        if (root == NULL) return result;        path.push_back(root->val); // 把根节点放进路径        traversal(root, sum - root->val);        return result;    }};

至于113. 路径总和II 的迭代法我并没有写,用迭代方式记录所有路径比较麻烦,也没有必要,如果大家感兴趣的话,可以再深入研究研究。

总结

本篇通过leetcode上112. 路径总和 和 113. 路径总和II 详细的讲解了 递归函数什么时候需要返回值,什么不需要返回值。

这两道题目是掌握这一知识点非常好的题目,大家看完本篇文章再去做题,就会感受到搜索整棵树和搜索某一路径的差别。

对于112. 路径总和,我依然给出了递归法和迭代法,这种题目其实用迭代法会复杂一些,能掌握递归方式就够了!

加个油!

-------end-------

我将算法学习相关的资料已经整理到了

Github :https://github.com/youngyangyang04/leetcode-master,里面还有leetcode刷题攻略、各个类型经典题目刷题顺序、思维导图,可以fork到自己仓库有空看一看一定会有所收获,顺便给一个star支持一下吧!

我是程序员Carl,哈工大师兄,先后在腾讯和百度打杂,这里每天8:35准时推送一道经典算法题目,我选择的每道题目都不是孤立的,而是由浅入深,环环相扣,帮你梳理算法知识脉络,轻松学算法!

@代码随想录 期待你的关注



推荐阅读
  • 题目解析给定 n 个人和 n 种书籍,每个人都有一个包含自己喜好的书籍列表。目标是计算出满足以下条件的分配方案数量:1. 每个人都必须获得他们喜欢的书籍;2. 每本书只能分配给一个人。通过使用深度优先搜索算法,可以系统地探索所有可能的分配组合,确保每个分配方案都符合上述条件。该方法能够有效地处理这类组合优化问题,找到所有可行的解。 ... [详细]
  • POJ 2482 星空中的星星:利用线段树与扫描线算法解决
    在《POJ 2482 星空中的星星》问题中,通过运用线段树和扫描线算法,可以高效地解决星星在窗口内的计数问题。该方法不仅能够快速处理大规模数据,还能确保时间复杂度的最优性,适用于各种复杂的星空模拟场景。 ... [详细]
  • 在安装 iOS 开发所需的 CocoaPods 时,用户可能会遇到多种问题。其中一个常见问题是,在执行 `pod setup` 命令后,系统无法连接到 GitHub 以更新 CocoaPods/Specs 仓库。这可能是由于网络连接不稳定、GitHub 服务器暂时不可用或本地配置错误等原因导致。为解决此问题,建议检查网络连接、确保 GitHub API 限制未被触发,并验证本地配置文件是否正确。 ... [详细]
  • 本文探讨了一种高效的算法,用于生成所有数字(0-9)的六位组合,允许重复使用数字,并确保这些组合的和等于给定的整数N。该算法通过优化搜索策略,显著提高了计算效率,适用于大规模数据处理和组合优化问题。 ... [详细]
  • 在洛谷 P1344 的坏牛奶追踪问题中,第一问要求计算最小割,而第二问则需要找到割边数量最少的最小割。通过为每条边附加一个单位权值,可以在求解最小割时优先选择边数较少的方案,从而同时解决两个问题。这种策略不仅简化了问题的求解过程,还确保了结果的最优性。 ... [详细]
  • Kafka 是由 Apache 软件基金会开发的高性能分布式消息系统,支持高吞吐量的发布和订阅功能,主要使用 Scala 和 Java 编写。本文将深入解析 Kafka 的安装与配置过程,为程序员提供详尽的操作指南,涵盖从环境准备到集群搭建的每一个关键步骤。 ... [详细]
  • 如何精通编程语言:全面指南与实用技巧
    如何精通编程语言:全面指南与实用技巧 ... [详细]
  • 使用 `git stash` 可以将当前未提交的修改保存到一个临时存储区,以便在后续恢复工作目录时使用。例如,在处理中间状态时,可以通过 `git stash` 命令将当前的所有未提交更改推送到一个新的储藏中,从而保持工作目录的整洁。此外,本文还将详细介绍如何解决 `git stash pop` 时可能出现的冲突问题,帮助用户高效地管理代码变更。 ... [详细]
  • 寒假作业解析:第三周 2月12日 第7题
    尽快完成之前的练习任务!每日一练2.1 Problem A Laurenty and Shop 的题目要求是选择两条不同的路线以最小化总的等待时间。简要分析:通过对比不同路线的等待时间,可以找到最优解。此问题可以通过动态规划或贪心算法来解决,具体取决于路线的复杂性和约束条件。 ... [详细]
  • 在C语言程序开发中,调试和错误分析是确保代码正确性和效率的关键步骤。本文通过一个简单的递归函数示例,详细介绍了如何编写和调试C语言程序。具体而言,我们将创建一个名为 `factorial.c` 的文件,实现计算阶乘的功能,并通过逐步调试来分析和解决可能出现的错误。此外,文章还探讨了常见的调试工具和技术,如GDB和断点设置,以帮助开发者高效地定位和修复问题。 ... [详细]
  • 本文将继续探讨 JavaScript 函数式编程的高级技巧及其实际应用。通过一个具体的寻路算法示例,我们将深入分析如何利用函数式编程的思想解决复杂问题。示例中,节点之间的连线代表路径,连线上的数字表示两点间的距离。我们将详细讲解如何通过递归和高阶函数等技术实现高效的寻路算法。 ... [详细]
  • Presto:高效即席查询引擎的深度解析与应用
    本文深入解析了Presto这一高效的即席查询引擎,详细探讨了其架构设计及其优缺点。Presto通过内存到内存的数据处理方式,显著提升了查询性能,相比传统的MapReduce查询,不仅减少了数据传输的延迟,还提高了查询的准确性和效率。然而,Presto在大规模数据处理和容错机制方面仍存在一定的局限性。本文还介绍了Presto在实际应用中的多种场景,展示了其在大数据分析领域的强大潜力。 ... [详细]
  • CTF竞赛中文件上传技巧与安全绕过方法深入解析
    CTF竞赛中文件上传技巧与安全绕过方法深入解析 ... [详细]
  • 深入解析HTTP网络请求API:从基础到进阶的全面指南
    本文全面解析了HTTP网络请求API,从基础到进阶,详细介绍了Android平台上的两种原生API——HttpUrlConnection和HttpClient。这两种API通过对底层Socket的封装,提供了高效、灵活的网络通信功能。文章不仅涵盖了基本的使用方法,还深入探讨了性能优化、错误处理和安全性等方面的高级主题,帮助开发者更好地理解和应用这些工具。 ... [详细]
  • 2012年9月12日优酷土豆校园招聘笔试题目解析与备考指南
    2012年9月12日,优酷土豆校园招聘笔试题目解析与备考指南。在选择题部分,有一道题目涉及中国人的血型分布情况,具体为A型30%、B型20%、O型40%、AB型10%。若需确保在随机选取的样本中,至少有一人为B型血的概率不低于90%,则需要选取的最少人数是多少?该问题不仅考察了概率统计的基本知识,还要求考生具备一定的逻辑推理能力。 ... [详细]
author-avatar
手机用户2502885897
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有