每日一题——力扣144. 二叉树的前序遍历(举一反三+思想解读+逐步优化)五千字好文

news2024/11/17 23:56:33


一个认为一切根源都是“自己不够强”的INTJ

个人主页:用哲学编程-CSDN博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数

Python-3.12.0文档解读

目录

 我的写法:

代码结构

时间复杂度

空间复杂度

总结

我要更强

代码说明

时间复杂度

空间复杂度

哲学和编程思想

迭代与递归:

空间与时间的权衡:

抽象与具体化:

数据结构的选择:

内存管理:

算法优化:

举一反三


题目链接:https://leetcode.cn/problems/binary-tree-preorder-traversal/description/

 我的写法:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
int TreeSize(struct TreeNode* root){
    return root == NULL ? 0 :1+TreeSize(root->left)+TreeSize(root->right);
}

void perorder(struct TreeNode* root,int* a,int* pi){
    if(root==NULL)
        return;
    
    a[(*pi)++]=root->val;
    perorder(root->left,a,pi);
    perorder(root->right,a,pi);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize){
    *returnSize=TreeSize(root);
    int* a=(int*)malloc(*returnSize*sizeof(int));

    int i=0;
    perorder(root,a,&i);

    return a;
}

这段代码实现了二叉树的前序遍历(Preorder Traversal),并返回一个包含遍历结果的动态数组。下面是对这段代码的专业点评:

代码结构

  1. TreeSize 函数:
    • 该函数用于计算二叉树的节点数量。它通过递归地遍历左子树和右子树,并累加节点数来实现。如果根节点为空,则返回0;否则,返回1加上左子树和右子树的节点数。
  2. perorder 函数:
    • 这是一个递归函数,用于执行前序遍历。前序遍历的顺序是:根节点 -> 左子树 -> 右子树。该函数将遍历到的节点值存储到数组 a 中,并通过引用传递的 pi 指针来更新数组索引。
  3. preorderTraversal 函数:
  • 这是主函数,负责分配返回数组的空间,调用 perorder 函数进行遍历,并返回遍历结果。它首先通过 TreeSize 函数确定返回数组的大小,然后分配相应大小的内存空间,并调用 perorder 函数填充数组。

时间复杂度

  • TreeSize 函数:O(n),其中 n 是树中的节点数。每个节点都被访问一次。
  • perorder 函数:O(n),同样地,每个节点都被访问一次。
  • preorderTraversal 函数:O(n),因为它调用了 perorder 函数,而 perorder 函数的时间复杂度是 O(n)。

空间复杂度

  • TreeSize 函数:O(h),其中 h 是树的高度。在最坏的情况下,递归调用的栈深度等于树的高度。
  • perorder 函数:O(h),同样地,递归调用的栈深度等于树的高度。
  • preorderTraversal 函数:O(n),因为需要分配一个大小为 n 的数组来存储遍历结果。此外,递归调用的栈空间也是 O(h)。

总结

这段代码有效地实现了二叉树的前序遍历,并且时间复杂度和空间复杂度都是合理的。注意,空间复杂度中的 O(h) 部分是由于递归调用栈的使用,这在树的高度较大时可能会成为一个问题。在实际应用中,需要考虑树的平衡性以及可能的最大高度。


我要更强

为了优化时间复杂度和空间复杂度,我们可以考虑使用迭代方法而不是递归来实现前序遍历。这样可以避免递归调用栈的空间开销,并且时间复杂度仍然是 O(n),因为每个节点仍然只被访问一次。

下面是使用迭代方法实现的完整代码:

#include <stdlib.h>
#include <stdbool.h>

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    if (root == NULL) {
        *returnSize = 0;
        return NULL;
    }

    int* result = (int*)malloc(100 * sizeof(int)); // 假设最多100个节点,可根据实际情况调整
    struct TreeNode** stack = (struct TreeNode**)malloc(100 * sizeof(struct TreeNode*)); // 栈空间
    int top = -1; // 栈顶指针
    int index = 0; // 结果数组索引

    stack[++top] = root; // 根节点入栈

    while (top >= 0) {
        struct TreeNode* node = stack[top--]; // 出栈
        result[index++] = node->val; // 访问节点

        // 由于前序遍历是根-左-右,先右子树入栈,再左子树入栈
        if (node->right != NULL) {
            stack[++top] = node->right;
        }
        if (node->left != NULL) {
            stack[++top] = node->left;
        }
    }

    *returnSize = index; // 设置返回数组大小
    free(stack); // 释放栈空间
    return result;
}

代码说明

  1. 栈的使用:我们使用一个栈来模拟递归调用的过程。在迭代方法中,我们手动管理这个栈。
  2. 前序遍历的顺序:根节点首先被访问,然后是左子树,最后是右子树。因此,我们在栈中先压入右子树,然后是左子树,这样在出栈时,左子树会在右子树之前被访问。
  3. 内存管理:我们动态分配了栈和结果数组的空间。在函数结束前,我们释放了栈的空间,但结果数组的空间由调用者负责释放。

时间复杂度

  • O(n):每个节点被访问一次。

空间复杂度

  • O(h):在最坏情况下,栈的大小等于树的高度。在最好情况下(平衡树),空间复杂度为 O(log n)。

这种方法在空间效率上优于递归方法,因为它避免了递归调用栈的空间开销。然而,它仍然需要一个栈来模拟递归过程,因此在最坏情况下的空间复杂度仍然是 O(h)。如果树非常不平衡,这可能会导致较高的空间使用。


哲学和编程思想

这些方法体现了以下哲学和编程思想:

  1. 迭代与递归:

    • 递归是一种自我调用的方法,它依赖于调用栈来保存状态。递归通常使得代码更简洁、易于理解,但在处理大量数据时可能会导致栈溢出。
    • 迭代是一种循环结构,它通过显式地管理状态来避免递归的缺点。迭代通常更节省空间,因为它不需要额外的栈空间来保存中间状态。
    • 在上述代码中,我们使用了迭代方法来实现前序遍历,这体现了在空间效率和性能之间的权衡。
  2. 空间与时间的权衡:

    • 在编程中,我们经常需要在空间复杂度和时间复杂度之间做出权衡。例如,使用额外的空间(如栈)来存储中间结果,可以减少时间复杂度。
    • 在迭代方法中,我们牺牲了一定的空间(栈空间)来减少递归调用栈的空间开销,从而优化了整体的空间复杂度。
  3. 抽象与具体化:

    • 抽象是指忽略问题的某些细节,专注于核心概念。在二叉树遍历中,我们抽象出了遍历的顺序(根-左-右),而不关心具体的实现细节。
    • 具体化是指将抽象的概念转化为具体的实现。在迭代方法中,我们将抽象的遍历顺序具体化为栈的操作,通过入栈和出栈来实现遍历。
  4. 数据结构的选择:

    • 选择合适的数据结构是编程中的一个重要决策。在二叉树遍历中,我们选择了栈作为辅助数据结构,因为它可以有效地存储和访问节点的顺序。
    • 栈的先进后出(LIFO)特性非常适合模拟递归调用的过程,使得我们可以在迭代中实现与递归相同的前序遍历顺序。
  5. 内存管理:

    • 在C语言中,内存管理是一个重要的编程实践。我们手动分配和释放内存,以避免内存泄漏和悬挂指针。
    • 在代码中,我们动态分配了栈和结果数组的空间,并在不再需要时释放了栈的空间,这体现了良好的内存管理习惯。
  6. 算法优化:

  • 算法优化是提高程序效率的关键。在迭代方法中,我们通过优化栈的使用顺序(先右后左)来确保前序遍历的正确性,这是一种算法层面的优化。

这些哲学和编程思想是解决复杂问题的基石,它们帮助我们在设计和实现代码时做出明智的决策,以达到高效、可维护和可扩展的解决方案。


举一反三

根据上述哲学和编程思想,以下是一些技巧和策略,可以帮助在面对类似问题时举一反三:

  1. 理解问题本质:
    • 在解决任何问题之前,首先要深入理解问题的本质。例如,在二叉树遍历问题中,理解前序遍历的定义(根-左-右)是至关重要的。
    • 技巧:将问题抽象为基本概念,然后从这些概念出发寻找解决方案。
  2. 选择合适的数据结构:
    • 根据问题的特点选择合适的数据结构。例如,栈在处理需要逆序操作的问题时非常有用。
    • 技巧:熟悉各种数据结构的特性,并能够根据问题的需求灵活选择。
  3. 迭代与递归的转换:
    • 学会将递归算法转换为迭代算法,或者反之。这种转换通常涉及到使用栈或队列来模拟递归调用栈。
    • 技巧:练习将递归算法重写为迭代算法,以提高对这两种方法的理解和应用能力。
  4. 空间与时间的权衡:
    • 在设计算法时,考虑时间和空间的权衡。有时候,牺牲一些空间可以显著提高时间效率。
    • 技巧:评估不同解决方案的时间和空间复杂度,选择最合适的平衡点。
  5. 内存管理:
    • 在需要手动管理内存的编程语言中,如C语言,注意内存的分配和释放。
    • 技巧:养成良好的内存管理习惯,确保在不再需要内存时及时释放。
  6. 算法优化:
    • 不断寻找算法优化的可能性。例如,通过改变数据结构的使用顺序或方式来提高效率。
    • 技巧:分析算法的瓶颈,尝试不同的优化策略,如减少不必要的计算或改进数据访问模式。
  7. 抽象与具体化:
    • 学会将复杂问题抽象为简单的模型,然后将这些模型具体化为可执行的代码。
    • 技巧:练习将问题分解为更小、更易于管理的部分,然后逐步构建解决方案。
  8. 代码复用与模块化:
    • 在编写代码时,考虑代码的复用性和模块化。这有助于提高代码的可维护性和可扩展性。
    • 技巧:设计可重用的函数和类,将代码分解为独立的模块。
  9. 测试与调试:
  • 编写测试用例来验证代码的正确性,并使用调试工具来定位和修复错误。
  • 技巧:编写全面的测试用例,使用调试工具逐步跟踪代码执行过程。

通过实践这些技巧和策略,将能够更好地理解和解决各种编程问题,提高你的编程能力和问题解决能力。记住,编程是一个不断学习和实践的过程,通过不断的练习和挑战,将能够举一反三,解决更复杂的问题。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1891146.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql 忘记 root 密码的解决办法(针对不同 mysql 版本)

文章目录 1.前提说明1.1 cmd 窗口打开方式1.2 mysql 服务相关命令知识补充1.3 三个 mysql 版本说明1.4 运行时可能发生的报错问题&#x1f340; 跳过密码授权命令报错&#x1f340; 修改密码时报错&#x1f340; ERROR 2003 (HY000): Cant connect to MySQL server on localhos…

安卓稳定性之crash详解

目录 前言一、Crash 的基本原理二、Crash 分析思路三、实例分析四、预防措施五、参考链接 前言 在开发和测试 Android 应用程序时&#xff0c;遇到应用程序崩溃是很常见的情况。 Android 崩溃指的是应用程序因为异常或错误而无法正常执行&#xff0c;并且导致应用强制关闭。 一…

RabbitMQ 进程内流控(Flow Control) 源码解析

1. 概述 1.1 为什么要流控&#xff1f; 流控主要是为了防止生产者生产消息速度过快&#xff0c;超过 Broker 可以处理的速度。这时需要暂时限制生产者的生产速度&#xff0c;让 Broker 的处理能够跟上生产速度。 Erlang进程之间不共享内存&#xff0c;每个进程都有自己的进程邮…

什么是眼球凹渲染?如何在Varjo Base中设置眼球追踪与凹渲染功能

当谈到Varjo耳机时&#xff0c;它们总是与超高分辨率显示器和有凹渲染联系在一起&#xff0c;从而能够高效地渲染到高像素的显示器上。 本篇博文的主题是分享一些可用于OpenXR应用程序的新设置&#xff0c;但在此之前&#xff0c;让我们先了解关于有凹渲染的一些背景。有凹渲染…

【计算机毕业设计】基于Springboot的B2B平台医疗病历交互系统【源码+lw+部署文档】

包含论文源码的压缩包较大&#xff0c;请私信或者加我的绿色小软件获取 免责声明&#xff1a;资料部分来源于合法的互联网渠道收集和整理&#xff0c;部分自己学习积累成果&#xff0c;供大家学习参考与交流。收取的费用仅用于收集和整理资料耗费时间的酬劳。 本人尊重原创作者…

Python爬取国家医保平台公开数据

国家医保服务平台数据爬取python爬虫数据爬取医疗公开数据 定点医疗机构查询定点零售药店查询医保机构查询药品分类与代码查询 等等&#xff0c;数据都能爬 接口地址&#xff1a;/ebus/fuwu/api/nthl/api/CommQuery/queryFixedHospital 签名参数&#xff1a;signData {dat…

H5打包失败:JS堆内存不足

傻子没看懂报错&#xff0c;像个无头苍蝇 解决方式&#xff1a;清空缓存&#xff0c;重启电脑&#xff0c;打包成功。

二叉树中序遍历-递归法详解-数据结构与算法

首先看下中序遍历的代码&#xff1a;&#xff08;左 跟 右&#xff09; 其首先要接受一个根结点root作为参数 判断根节点是否为NULL 不为NULL则递归遍历左子树 ①我们把树根结点A传递给它 其左结点为B&#xff0c;右结点为C ②首先我们要检查root是否为NULL 其不为NULL …

使用pyinstaller 如何打包python项目

参考&#xff1a;【python项目正确打包方法-哔哩哔哩】 https://b23.tv/EDB6zbG Pyinstaller 详解多种打包过程(去坑,填坑)。_pyinstaller -f -w-CSDN博客 1.打开命令提示符&#xff1a; 找到python项目所在位置&#xff0c;输入cmd即可 2. 安装pipenv: 在命令提示符&#…

1000T的文件怎么能快速从南京传到北京?最佳方案你肯定想不到

今天刷面试题看到一个有意思的面试题&#xff0c; 1000T的文件怎么能以最快速度从南京传到北京&#xff1f; 网络传输 首先我们考虑通过网络传输&#xff0c;需要多长时间。 我特地咨询了在运营商工作的同学&#xff0c;目前带宽&#xff1a; 家庭宽带下行最大1Gbps&#…

x264 编码器汇编模块介绍

aarch64汇编架构 解释:AArch64 是 ARM 架构的 64 位版本,也称为 ARMv8-A特点: 64位寻址能力,支持更大的地址空间,理论上可达16EB(Exabyte)使用64位宽的寄存器,有31个通用寄存器(X0-X30),外加一个链接寄存器(X31)支持扩展的 NEON SIMD 指令集,提供更多的执行单元和…

电脑显示由于找不到MSVCP140.dll,无法继续执行代码

电脑已经成为我们生活和工作中不可或缺的工具&#xff0c;然而&#xff0c;在使用电脑的过程中&#xff0c;我们常常会遇到一些错误提示&#xff0c;其中之一就是“电脑显示由于找不到MSVCP140.dll是怎么回事&#xff1f;”这个问题。小编将详细介绍该问题的原因、解决方法以及…

新版本发布丨昂辉科技EasySAR-Configurator V1.2.0再启航

昂辉科技新一代跨平台高性能AUTOSAR配置工具EasySAR-Configurator V1.2.0全新版本重磅发布&#xff01;产品基于Web架构前后端分离的方式开发&#xff0c;可提供SaaS部署&#xff0c;能够实现精准配置和最大限度的代码裁剪&#xff0c;且配备标准的约束限制、配置验证、代码生成…

技术成神之路:设计模式(二)建造者模式

1.定义 建造者模式&#xff08;Builder Pattern&#xff09;是一种创建型设计模式&#xff0c;它允许你分步骤创建复杂对象&#xff0c;而不必直接调用构造函数。建造者模式特别适合那些包含多个组成部分并且构造过程复杂的对象。 2. 结构 建造者模式的主要组成部分包括&#…

基于字符和词特征融合的恶意域名检测

传统的恶意域名检测方法在检测由域名生成算法&#xff08;DGA&#xff09;随机生成的恶意域名方面性能不佳&#xff0c;尤其是对于那些由随机单词组成的域名。文章提出了一种新的检测算法&#xff0c;通过融合字符和词特征来提高对恶意域名的检测能力&#xff0c;特别是对于更具…

【RT摩拳擦掌】如何构建RT AVB switchendpoint平台

【RT摩拳擦掌】如何构建RT AVB switch&endpoint平台 一&#xff0c;文档简介二&#xff0c;平台构建2.1 软硬件情况2.2 配置RT1170 AVB端点2.2.1 1块MIMXRT1170开发板做talker配置2.2.2 2块MIMXRT1170开发板做listener配置 2.3 AVB Switch 配置2.3.1 MOTU AVB Switch2.3.2 …

【鸿蒙学习笔记】@Prop装饰器:父子单向同步

官方文档&#xff1a;Prop装饰器&#xff1a;父子单向同步 [Q&A] Prop装饰器作用 Prop装饰的变量可以和父组件建立单向的同步关系。Prop装饰的变量是可变的&#xff0c;但是变化不会同步回其父组件。 [Q&A] Prop装饰器特点 &#xff11;・Prop装饰器不能在Entry装饰的…

关于ant design vue 使用Modal无法关闭弹窗的解决思路

文章目录 1: 出现问题的版本2.出现问题&#xff08;1&#xff09;ant design 的问题&#xff08;2&#xff09;poina的提示报错 3.正确版本总结 1: 出现问题的版本 "ant-design-vue": "^3.2.20", "pinia": "^2.1.7", "vue"…

Mybatis Plus 自动填充注解 @TableField(fill = FieldFill.INSERT_UPDATE)

第一步&#xff1a;在需要自动填充的位置加上注解 通过在创建时间和修改时间上添加 fill 填充字段 进行自动填充 第二步&#xff1a;要想实现自动填充还需要实现MetaObjectHandler接口&#xff0c;在这里实现自动填充的逻辑 Component public class MyMetaObjectHandler …

python sklearn机械学习-数据预处理

&#x1f308;所属专栏&#xff1a;【机械学习】✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您…