当前位置: 首页 > news >正文

Leetcode 47

1 题目

117. 填充每个节点的下一个右侧节点指针 II

给定一个二叉树:

struct Node {int val;Node *left;Node *right;Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL 。

初始状态下,所有 next 指针都被设置为 NULL 。

示例 1:

输入:root = [1,2,3,4,5,null,7]
输出:[1,#,2,3,#,4,5,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化输出按层序遍历顺序(由 next 指针连接),'#' 表示每层的末尾。

示例 2:

输入:root = []
输出:[]

提示:

  • 树中的节点数在范围 [0, 6000] 内
  • -100 <= Node.val <= 100

进阶:

  • 你只能使用常量级额外空间。
  • 使用递归解题也符合要求,本题中递归程序的隐式栈空间不计入额外空间复杂度。

2 代码实现

/*** Definition for a Node.* struct Node {*     int val;*     struct Node *left;*     struct Node *right;*     struct Node *next;* };*/
typedef struct Node Node ;
Node* getNext(Node * node ){while (node ){if (node -> left ) return node -> left ;if (node -> right) return node -> right ;node = node -> next;}return NULL ;}
void traverse(Node* node){if (!node)return;if(node -> left){node -> left -> next = node -> right ? node -> right : getNext(node -> next);}if(node -> right){node -> right -> next =  getNext(node -> next);}traverse(node -> right);traverse(node -> left );
}
struct Node* connect(struct Node* root) {if(!root) return NULL ;traverse (root);return root;
}

Leetcode 46-CSDN博客

lc116里面的一般请况解决版本,对于任意二叉树而不是完美二叉树。

/*** Definition for a Node.* struct Node {*     int val;*     struct Node *left;*     struct Node *right;*     struct Node *next;* };*/
typedef struct Node Node;// 辅助函数:找到node的下一个可用的子节点(用于连接堂兄弟)
Node* findNext(Node* node) {if (node == NULL) return NULL;// 优先返回左孩子,左孩子不存在则返回右孩子if (node->left) return node->left;if (node->right) return node->right;// 若当前节点无孩子,递归找下一个节点的孩子return findNext(node->next);
}void connectRecursive(Node* node) {if (node == NULL) return;// 处理左孩子的nextif (node->left) {if (node->right) {// 左孩子next指向右孩子(同父母)node->left->next = node->right;} else {// 左孩子next指向堂兄弟(通过父节点的next查找)node->left->next = findNext(node->next);}}// 处理右孩子的nextif (node->right) {// 右孩子next指向堂兄弟(通过父节点的next查找)node->right->next = findNext(node->next);}// 先递归处理右子树(确保查找堂兄弟时右子树已初始化)connectRecursive(node->right);// 再递归处理左子树connectRecursive(node->left);
}struct Node* connect(struct Node* root) {if (root == NULL) return NULL;root->next = NULL; // 根节点next为NULLconnectRecursive(root);return root;
}

错误百出的初版代码

/*** Definition for a Node.* struct Node {*     int val;*     struct Node *left;*     struct Node *right;*     struct Node *next;* };*/
typedef struct Node Node ;
Node getNext(Node * node ){while (node ){if (node -> left ) return node -> left ;if (node -> right) return node -> right ;node = node -> next;}return NULL ;}
void traverse(Node node){if (!node)return;if(node -> left){node -> left -> next = node -> right ? node -> right : getNext(node -> next);}if(node -> right){node -> right -> next =  getNext(node -> next);}traverse(node -> left );traverse(node -> right );
}
struct Node* connect(struct Node* root) {if(!root) return NULL ;traverse (root);return root;
}

这段代码存在多处语法错误和逻辑问题,导致无法正常编译和运行,下面详细分析每一处问题:

1. getNext 函数返回值类型错误

  • 问题:函数声明为 Node getNext(...),表示返回一个 Node 结构体对象,但实际返回的是 Node*(结构体指针,如 return node->left),类型完全不匹配。
  • 后果:编译时会提示 “返回类型与函数声明不匹配” 的错误。
  • 修正:应将返回值类型改为 Node*,即 Node* getNext(Node* node)

2. traverse 函数参数类型错误

  • 问题:函数声明为 void traverse(Node node),参数是 Node 结构体对象,但函数内部却使用 node->leftnode->right 这样的指针操作(-> 是指针运算符)。
  • 后果:对非指针类型使用 -> 会直接导致编译错误,因为只有指针才能通过 -> 访问成员。
  • 修正:参数应改为指针类型,即 void traverse(Node* node)

3. traverse 函数内部空指针判断逻辑错误

  • 问题:if (!node) return; 这行代码本身逻辑没问题(判断指针是否为空),但结合参数类型错误来看:如果参数是 Node node(非指针),!node 会试图将结构体对象转换为布尔值,这在 C 语言中是不允许的(结构体不能直接判断真假)。
  • 后果:即使修正了参数类型,若参数为非指针,此处会编译错误;若参数改为指针(正确做法),这行逻辑才有效。

4. traverse 函数递归调用参数错误

  • 问题:traverse(node -> left); 和 traverse(node -> right); 中,node->left 和 node->right 是 Node* 类型(指针),但 traverse 函数声明的参数是 Node 类型(非指针)。
  • 后果:指针类型无法隐式转换为结构体对象类型,编译时会提示 “参数类型不匹配” 错误。
  • 修正:在 traverse 函数参数改为 Node* node 后,这两处调用才合法(指针传参)。

5. 递归顺序逻辑错误(功能层面)

  • 问题:即使修正了上述语法错误,当前代码先递归左子树 traverse(node->left),再递归右子树 traverse(node->right),会导致 getNext 函数可能无法正确找到目标节点。
  • 原因:右孩子的 next 指针依赖父节点 next 链指向的右侧节点(可能是叔叔节点或更远的节点),若先处理左子树,右侧节点的 next 链可能尚未初始化,getNext 无法获取有效节点。
  • 修正:应先递归右子树,再递归左子树(traverse(node->right); traverse(node->left);),确保右侧节点的 next 链先初始化。

总结所有问题及修正方向

问题位置具体错误修正方案
getNext 返回值类型声明为 Node,实际返回 Node*改为 Node* getNext(Node* node)
traverse 参数类型声明为 Node,实际需要指针操作改为 void traverse(Node* node)
traverse 内部 -> 操作对非指针使用 -> 运算符配合参数类型修正,改为指针后合法
traverse 递归参数实参为 Node*,形参为 Node配合参数类型修正,传指针合法
递归顺序先左后右导致 next 链未初始化改为先右后左:traverse(right); traverse(left)

修正后的完整代码

/*** Definition for a Node.* struct Node {*     int val;*     struct Node *left;*     struct Node *right;*     struct Node *next;* };*/
typedef struct Node Node;// 修正返回值类型为 Node*
Node* getNext(Node* node) {while (node) {if (node->left) return node->left;if (node->right) return node->right;node = node->next;}return NULL;
}// 修正参数类型为 Node*
void traverse(Node* node) {if (!node)  // 对指针判断空值,合法return;// 处理左孩子的 next 指针if (node->left) {node->left->next = node->right ? node->right : getNext(node->next);}// 处理右孩子的 next 指针if (node->right) {node->right->next = getNext(node->next);}// 修正递归顺序:先右后左traverse(node->right);traverse(node->left);
}struct Node* connect(struct Node* root) {if (!root) return NULL;traverse(root);return root;
}

修正后,代码可正确处理任意二叉树的 next 指针连接,通过递归先处理右侧节点,确保 getNext 能利用已初始化的 next 链找到目标节点。

【next指针像一条小尾巴,起到的是连接作用,而不是left right物理上的左节点or右节点】

next 指针确实像给每个节点加了一条 “横向的小尾巴”,它的核心作用是建立同一层节点之间的横向连接,和 left/right 这种 “纵向父子关系” 的物理结构完全不同。

具体来说,这两种指针的本质区别在于:

  • left/right 是 “血缘关系”:固定指向当前节点的左 / 右孩子,是二叉树结构本身的一部分,一旦树的形态确定,它们的指向就固定了。
  • next 是 “邻居关系”:动态指向当前节点在同一层的 “右侧邻居”,是人为添加的横向关联,目的是快速访问同层节点(比如从一个节点直接跳到同层下一个,不用再从根节点重新遍历)。

理解这一点,就能更清晰地把握 next 指针的设计逻辑:比如在处理任意二叉树时,next 指针的指向不依赖于当前节点的左右孩子是否存在,而是要 “跨父节点” 找同层的下一个有效节点 —— 就像邻居可能是隔壁(同一个父节点的右 sibling),也可能是对门(父节点的 next 指向的节点的孩子),甚至更远的住户。

这种 “横向尾巴” 的视角,也能帮你更直观地理解递归处理的顺序:为什么要先处理右子树?因为右侧节点的 “尾巴”(next)可能会成为左侧节点 “尾巴” 的指向目标,得先让右侧邻居 “就位”,左侧才能准确找到自己的 “尾巴该勾向哪里”。

3 学习建议

【好难啊,才做了一题算法,这么不容易,漏洞逻辑错误百出,基础要补补,怎么补】

算法题做得不顺利、出现各种漏洞是非常正常的,尤其是刚开始练习的时候。基础薄弱并不可怕,关键是有系统的方法去补。结合你的情况(刚接触二叉树这类递归题,容易出语法和逻辑错误),可以按这个步骤补基础:

一、先补「C 语言基础语法」—— 避免低级错误

你之前的代码里出现了「指针与结构体混淆」「函数返回值类型不匹配」等问题,这些是语法层面的基础问题,必须先解决。

  1. 重点掌握 3 个核心概念

    • 指针与结构体:明确 Node(结构体对象)和 Node*(结构体指针)的区别,记住 -> 只能用于指针,. 用于对象。
    • 函数参数与返回值:函数声明的返回值类型(如 void/int/Node*)必须和实际 return 的内容一致。
    • 递归函数的终止条件:递归一定要有 if (nullptr) 这类终止判断,否则会无限递归导致崩溃。
  2. 练习方式:找一些简单的「结构体 + 指针」小题目练手,比如:

    • 定义一个链表节点结构体,实现链表的创建、插入、打印。
    • 定义一个二叉树节点结构体,手动构建一棵简单的树(比如 3 个节点),并遍历打印节点值。先保证写出来的代码能编译通过,再考虑逻辑对错。

二、用「拆解法」补「数据结构基础」—— 从简单结构开始

二叉树、递归这些内容难,是因为你对「结构的操作逻辑」不熟悉。可以从「线性结构」到「非线性结构」逐步拆解学习:

  1. 先吃透「链表」:链表是二叉树的简化版(每个节点只有一个 “孩子”),很多二叉树的逻辑(如遍历、指针连接)都能在链表中找到影子。

    • 练熟「链表的遍历」(迭代 / 递归)、「链表的指针修改」(如反转链表、连接两个链表)。
    • 思考:链表中 “当前节点的下一个节点” 如何找?类比到二叉树中 “当前节点孩子的 next 指针” 该怎么找?(你之前的 getNext 函数其实和链表遍历逻辑类似)
  2. 再啃「二叉树」:从「二叉树的遍历」开始,这是所有二叉树题的基础:

    • 手动模拟「前中后序递归遍历」:拿一张纸,画一棵二叉树,一步一步写递归过程中每个节点的访问顺序,理解递归的 “栈” 是怎么工作的。
    • 重点理解「递归的子问题拆分」:比如 “连接二叉树的 next 指针”,可以拆成 “当前节点的左孩子 next 指向谁”“右孩子 next 指向谁”“左子树和右子树分别处理”,每个子问题和原问题结构相同,这就是递归的核心。

三、用「刻意练习」补「算法逻辑思维」—— 从模仿到独立

刚开始写算法题,不要怕 “抄” 和 “模仿”,关键是理解别人的逻辑为什么对,自己的错在哪里。

  1. 对一道题,做 3 遍以上

    • 第一遍:看题解,看懂后照抄代码,运行通过,标记出自己之前没想到的逻辑(比如你之前递归顺序搞反了,就要记 “为什么先处理右子树”)。
    • 第二遍:隔 1-2 天,不看题解,自己写。如果卡壳,只看提示(比如 “递归顺序应该先右后左”),然后继续写。
    • 第三遍:再隔几天,完全独立写,写完后对比自己的代码和最优解,分析哪里可以优化(比如是否有多余的判断)。
  2. 积累「常见逻辑模板」:比如二叉树的递归模板:

    void traverse(Node* node) {if (node == NULL) return; // 终止条件// 处理当前节点(前序)traverse(node->left);   // 处理左子树// 处理当前节点(中序)traverse(node->right);  // 处理右子树// 处理当前节点(后序)
    }
    

    很多题(包括你的 connect 函数)都是在这个模板上改的,只是 “处理当前节点” 的逻辑不同。你需要记住这个模板,然后思考 “当前节点需要做什么操作”。

四、工具辅助:用「画图」和「调试」理解错误

  1. 画图模拟过程:遇到递归或指针操作的问题,拿一张纸画出来:

    • 画一棵具体的二叉树(比如只有 3-5 个节点,包含空节点)。
    • 按代码的步骤,一步步标出来每个指针的指向(比如 node->left->next 应该指向谁,实际指向了谁)。你会发现很多逻辑错误(比如递归顺序错)在图上一目了然。
  2. 学会调试代码:用 IDE(如 VS Code、Clion)的调试功能,一步步执行代码:

    • 观察每个变量的值(比如 node 是不是 NULLnode->next 指向哪里)。
    • 看函数调用栈(递归到了哪一层,当前处理的是哪个节点)。调试多了,你会直观感受到代码的执行流程,更容易发现错误。

五、心态:接受 “不完美”,从「小进步」开始

刚开始练算法,没人能一次写对。你现在能发现自己 “漏洞百出”,其实已经是进步了 —— 至少知道哪里错了。

  • 每天只做 1-2 题,但每道题必须搞懂所有错误原因(是语法错还是逻辑错)。
  • 记录一个「错题本」,把自己常犯的错误(比如指针类型搞混、递归顺序错)列出来,每次写代码前扫一眼,提醒自己注意。

按这个节奏坚持 1-2 个月,你会明显感觉到:语法错误越来越少,看递归题的逻辑会越来越清晰,甚至能预判出可能出错的地方。基础就是这样一点点补起来的,慢慢来,别急~

慢慢来,比较快。

http://www.dtcms.com/a/582479.html

相关文章:

  • 营销型网站分类自己服务器可以做网站
  • EtherCAT命令整理
  • Windows 常用命令行(CMD/PowerShell 通用,标注差异)
  • 小迪安全v2023学习笔记(一百四十五讲)—— Webshell篇魔改冰蝎打乱特征指纹新增加密协议过后门查杀过流量识别
  • 网站源码做exe执行程序域名被墙查询检测
  • HarmonyOS:ArkWeb在新窗口中打开页面
  • 青岛谁做网站多少钱做网站大概需要多少费用
  • jmeter内存踩坑记录
  • 浙江建设职业技术学院网站彬县网
  • PowerShell 和 CMD
  • EFS `<br>` 标签渲染修复:从文本到换行的完整解决方案
  • 怎样在建设厅网站查询安全员证彩票网站开发与建设
  • 创建一个网站要钱吗梅林网站建设公司
  • 成都小程序定制开发企业网站怎样做seo优化 应该如何做
  • Java中的设计模式------策略设计模式
  • 太原做网站设计电子商务网站设计原理书籍
  • 网站服务器迁移企业管理咨询机构
  • Redis —— 架构概览
  • 筑牢用电防线:Acrel-1000 自动化系统赋能 35kV 园区高效供电-安科瑞黄安南
  • 青海住房和城乡建设部网站山东省城乡建设厅官网
  • 哈尔滨智能建站模板厦门 网站建设 网站开发
  • 第3节 RSA算法开启公钥加密时代
  • 昆山做网站公司哪家好青岛市黄岛区城市建设局 网站
  • 从正确到卓越:昇腾CANN算子开发高级性能优化指南
  • 网站建设 国家标准微网站自助建站
  • 政务公开系统网站建设短剧分销平台
  • 网站建设的静态网页作业青田网站做服装找工作
  • 【1min 速通 -- PyTorch 张量数据类型】张量类型的获取、转化与判别
  • git stash push 命令作用及使用场景
  • 青岛李沧区城乡建设局网站自己给网站做优化怎么做