590. N 叉树的后序遍历 :「递归」&「非递归」&「通用非递归」

简介: 590. N 叉树的后序遍历 :「递归」&「非递归」&「通用非递归」

网络异常,图片无法展示
|


题目描述



这是 LeetCode 上的 590. N 叉树的后序遍历 ,难度为 简单


Tag : 「递归」、「迭代」、「非递归」、「DFS」、「BFS」


给定一个 nn 叉树的根节点 rootroot ,返回 其节点值的 后序遍历


nn 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。


示例 1:


网络异常,图片无法展示
|


输入:root = [1,null,3,2,4,null,5,6]
输出:[5,6,3,2,4,1]
复制代码


示例 2:


网络异常,图片无法展示
|


输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:[2,6,14,11,7,3,12,8,4,13,9,10,5,1]
复制代码


提示:


  • 节点总数在范围 [0, 10^4][0,104]
  • 0 <= Node.val <= 10^40<=Node.val<=104
  • nn 叉树的高度小于或等于 10001000


进阶:递归法很简单,你可以使用迭代法完成此题吗?


递归



常规做法,不再赘述。


代码:


class Solution {
    List<Integer> ans = new ArrayList<>();
    public List<Integer> postorder(Node root) {
        dfs(root);
        return ans;
    }
    void dfs(Node root) {
        if (root == null) return;
        for (Node node : root.children) dfs(node);
        ans.add(root.val);
    }
}
复制代码


  • 时间复杂度:O(n)O(n)
  • 空间复杂度:忽略递归带来的额外空间开销,复杂度为 O(1)O(1)


非递归



针对本题,使用「栈」模拟递归过程。


迭代过程中记录 (cnt = 当前节点遍历过的子节点数量, node = 当前节点) 二元组,每次取出栈顶元素,如果当前节点已经遍历完所有的子节点(当前遍历过的子节点数量为 cnt = 子节点数量cnt=),则将当前节点的值加入答案。


否则更新当前元素遍历过的子节点数量,并重新入队,即将 (cnt + 1, node)(cnt+1,node) 入队,以及将下一子节点 (0, node.children[cnt])(0,node.children[cnt]) 进行首次入队。


代码:


class Solution {
    public List<Integer> postorder(Node root) {
        List<Integer> ans = new ArrayList<>();
        Deque<Object[]> d = new ArrayDeque<>();
        d.addLast(new Object[]{0, root});
        while (!d.isEmpty()) {
            Object[] poll = d.pollLast();
            Integer cnt = (Integer)poll[0]; Node t = (Node)poll[1];
            if (t == null) continue;
            if (cnt == t.children.size()) ans.add(t.val);
            if (cnt < t.children.size()) {
                d.addLast(new Object[]{cnt + 1, t});
                d.addLast(new Object[]{0, t.children.get(cnt)});      
            }
        }
        return ans;
    }
}
复制代码


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


通用「非递归」



另外一种「递归」转「迭代」的做法,是直接模拟系统执行「递归」的过程,这是一种更为通用的做法。


由于现代编译器已经做了很多关于递归的优化,现在这种技巧已经无须掌握。


在迭代过程中记录当前栈帧位置状态 loc,在每个状态流转节点做相应操作。


代码:


class Solution {
    public List<Integer> postorder(Node root) {
        List<Integer> ans = new ArrayList<>();
        Deque<Object[]> d = new ArrayDeque<>();
        d.addLast(new Object[]{0, root});
        while (!d.isEmpty()) {
            Object[] poll = d.pollLast();
            Integer loc = (Integer)poll[0]; Node t = (Node)poll[1];
            if (t == null) continue;
            if (loc == 0) {
                d.addLast(new Object[]{1, t});
                int n = t.children.size();
                for (int i = n - 1; i >= 0; i--) d.addLast(new Object[]{0, t.children.get(i)});
            } else if (loc == 1) {
                ans.add(t.val);
            }
        }
        return ans;
    }
}
复制代码


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


最后



这是我们「刷穿 LeetCode」系列文章的第 No.590 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
Unix Linux 编译器
Linux创建临时文件mkstemp()tmpfile()
有些程序需要创建一些临时文件,仅供其在运行期间使用,程序终止后即行删除。 很多编译器程序会在编译过程中创建临时文件。GNU C 语言函数库为此而提供了一系列库函数。(之所以有“一系列”的库函数,部分原因是由于这些函数分别继承自各种 UNIX 实现。)本节将介绍其中的两个函数:mkstemp()和 tmpfile()。
256 0
Linux创建临时文件mkstemp()tmpfile()
|
开发者
LaTeX高效写作系列:word表格转LaTeX
Fancy版本见九天学者的个人博客,关注文集博士干点啥或者微信公众号九天学者及时获取连载更新。 如何将word表格转为格式 迫于无奈从刚开始学习计算机就上了某软这条贼船,不少情况下,将表格写为了word文件。
3553 0
LaTeX高效写作系列:word表格转LaTeX
|
12月前
|
PyTorch Linux 算法框架/工具
pytorch学习一:Anaconda下载、安装、配置环境变量。anaconda创建多版本python环境。安装 pytorch。
这篇文章是关于如何使用Anaconda进行Python环境管理,包括下载、安装、配置环境变量、创建多版本Python环境、安装PyTorch以及使用Jupyter Notebook的详细指南。
1406 1
pytorch学习一:Anaconda下载、安装、配置环境变量。anaconda创建多版本python环境。安装 pytorch。
|
8月前
|
开发框架 安全 .NET
掌握 LINQ:通过示例解释 C# 中强大的 LINQ的集运算
通过本文的示例,我们详细介绍了C#中LINQ的强大集合运算功能。LINQ提供了一种简洁、灵活和类型安全的方式来查询和操作数据集合,从而大大提高了代码的可读性和可维护性。希望本文能帮助读者更好地掌握和应用LINQ,提高开发效率。
211 13
|
算法 Linux C++
C++框架设计中实现可扩展性的方法
在软件开发中,可扩展性至关重要,尤其对于C++这样的静态类型语言。本文探讨了在C++框架设计中实现可扩展性的方法:1) 模块化设计降低耦合;2) 使用继承和接口实现功能扩展;3) 通过插件机制动态添加功能;4) 利用模板和泛型提升代码复用;5) 遵循设计原则和最佳实践;6) 应用配置和策略模式以改变运行时行为;7) 使用工厂和抽象工厂模式创建可扩展的对象;8) 实现依赖注入增强灵活性。这些策略有助于构建适应变化、易于维护的C++框架。
831 2
|
10月前
|
监控 安全 网络安全
防患于未然:如何构建抵御.sstop勒索病毒的防线
近年来,勒索病毒攻击事件频发,给个人和企业带来了巨大的安全威胁。其中,.sstop勒索病毒以其独特的加密手段和传播途径,成为网络安全领域的一大隐患。本文将详细介绍.sstop勒索病毒的特点,以及如何恢复被其加密的数据文件和预防措施。
|
传感器 监控 网络协议
Modbus协议详细解析与案例分享
Modbus协议详细解析与案例分享
535 0
|
12月前
|
存储 C语言
C语言:一维数组的不初始化、部分初始化、完全初始化的不同点
C语言中一维数组的初始化有三种情况:不初始化时,数组元素的值是随机的;部分初始化时,未指定的元素会被自动赋值为0;完全初始化时,所有元素都被赋予了初始值。
1200 2
|
存储 搜索推荐 数据建模
Elasticsearch 的数据建模与索引设计
【9月更文第3天】Elasticsearch 是一个基于 Lucene 的搜索引擎,广泛应用于全文检索、数据分析等领域。为了确保 Elasticsearch 的高效运行,合理的数据建模和索引设计至关重要。本文将探讨如何为不同的应用场景设计高效的索引结构,并分享一些数据建模的最佳实践。
462 2
|
SQL 数据采集 存储
Hive实战 —— 电商数据分析(全流程详解 真实数据)
关于基于小型数据的Hive数仓构建实战,目的是通过分析某零售企业的门店数据来进行业务洞察。内容涵盖了数据清洗、数据分析和Hive表的创建。项目需求包括客户画像、消费统计、资源利用率、特征人群定位和数据可视化。数据源包括Customer、Transaction、Store和Review四张表,涉及多个维度的聚合和分析,如按性别、国家统计客户、按时间段计算总收入等。项目执行需先下载数据和配置Zeppelin环境,然后通过Hive进行数据清洗、建表和分析。在建表过程中,涉及ODS、DWD、DWT、DWS和DM五层,每层都有其特定的任务和粒度。最后,通过Hive SQL进行各种业务指标的计算和分析。
2517 1
Hive实战 —— 电商数据分析(全流程详解 真实数据)