【剑指 の 精选】详解「二叉树中序遍历的下一个结点」两种解法

题目描述

这是「牛客网」上的**「JZ 57 二叉树的下一个结点」,难度为「中等」**。

Tag : 「剑指 Offer」、「二叉树」、「中序遍历」

给定一个二叉树其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。

注意,树中的结点不仅包含左右子结点,同时包含指向父结点的 next 指针。

下图为一棵有 个节点的二叉树。树中从父节点指向子节点的指针用实线表示,从子节点指向父节点的用虚线表示。

图片

示例:

输入:{8,6,10,5,7,9,11},8

返回:9

解析:这个组装传入的子树根节点,其实就是整颗树,中序遍历{5,6,7,8,9,10,11},根节点8的下一个节点就是9,应该返回{9,10,11},后台只打印子树的下一个节点,所以只会打印9,如下图,其实都有指向左右孩子的指针,还有指向父节点的指针,下图没有画出来。

图片

输入描述:输入分为2段,第一段是整体的二叉树,第二段是给定二叉树节点的值,后台会将这2个参数组装为一个二叉树局部的子树传入到函数GetNext里面,用户得到的输入只有一个子树根节点。

返回值描述:返回传入的子树根节点的下一个节点,后台会打印输出这个节点。

示例1

输入:{8,6,10,5,7,9,11},8

返回值:9

示例2

输入:{8,6,10,5,7,9,11},6

返回值:7

示例3

输入:{1,2,#,#,3,#,4},4

返回值:1

示例4

输入:{5},5

返回值:"null"

说明:不存在,后台打印"null" 

要求:

  • 时间:1 s

  • 空间:64 M

朴素解法

一个朴素的做法是,根据题目对于 TreeLinkNode 的定义,利用 next 属性存储「当前节点的父节点」这一特点。

从入参节点 pNode 出发,不断利用 next 属性往上查找,直到找到整棵树的头节点,令头节点为 root

然后实现二叉树的「中序遍历」,将遍历过程中访问的节点存放到列表 list 中,之后再对 list 进行遍历,找到 pNode 所在的位置 idx,即可确定 pNode 是否存在「下一个节点」以及「下一节点」是哪一个。

图片

代码:

import java.util.*;
public class Solution {
    List<TreeLinkNode> list = new ArrayList<>();
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        // 根据传入的节点的 next 指针一直往上找,直到找到根节点 root
        TreeLinkNode root = pNode;
        while (root.next != null) root = root.next;

        // 对树进行一遍「中序遍历」,保存结果到 list 中
        dfs(root);

        // 从 list 中找传入节点 pNode 的位置 idx
        int n = list.size(), idx = -1;
        for (int i = 0; i < n; i++) {
            if (list.get(i).equals(pNode)) {
                idx = i;
                break;
            }
        }
        
        // 如果 idx 不为「中序遍历」的最后一个元素,那么说明存在下一个节点,从 list 查找并返回
        // 这里的 idx == -1 的判断属于防御性编程
        return idx == -1 || idx == n - 1 ? null : list.get(idx + 1);
    }
    void dfs(TreeLinkNode root) {
        if (root == null) return;
        dfs(root.left);
        list.add(root);
        dfs(root.right);
    }
}

  • 时间复杂度:找头节点 root 最多访问的节点数量不会超过树高 ;进行中序遍历的复杂度为 ;从中序遍历结果中找到 pNode 的下一节点的复杂度为 。整体复杂度为

  • 空间复杂度:忽略递归带来的额外空间消耗。复杂度为

进阶解法

另外一个“进阶”的做法是充分利用「二叉树的中序遍历」来实现的。

我们知道,二叉树「中序遍历」的遍历顺序为 「左-根-右」

可以根据传入节点 pNode 是否有「右儿子」,以及传入节点 pNode 是否为其「父节点」的「左儿子」来进行分情况讨论:

  • 传入节点 pNode 有「右儿子」:根据「中序遍历」的遍历顺序为 「左-根-右」,可以确定「下一个节点」必然为当前节点的「右子树」中「最靠左的节点」;

  • 传入节点 pNode 没有「右儿子」,这时候需要根据当前节点是否为其「父节点」的「左儿子」来进行分情况讨论:

  • 如果传入节点 pNode 本身是其「父节点」的「左儿子」,那么根据「中序遍历」的遍历顺序为为 「左-根-右」 可知,下一个节点正是该父节点,直接返回该节点即可;

  • 如果传入节点 pNode 本身是其「父节点」的「右儿子」,那么根据「中序遍历」的遍历顺序为为 「左-根-右」 可知,其父节点已经被遍历过了,我们需要递归找到符合 node.equals(node.next.left) 的节点作为答案返回,如果没有则说明当前节点是整颗二叉树最靠右的节点,这时候返回 null 即可。

代码:

public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        if (pNode.right != null) {
            // 如果当前节点有右儿子,下一节点为当前节点「右子树中最靠左的节点」
            pNode = pNode.right;
            while (pNode.left != null) pNode = pNode.left;
            return pNode;
        } else {
            // 如果当前节点没有右儿子,则「往上找父节点」,直到出现满足「其左儿子是当前节点」的父节点
            while (pNode.next != null) {
                if (pNode.equals(pNode.next.left)) return pNode.next;
                pNode = pNode.next;
            }
        }
        return null;
    }
}

  • 时间复杂度:

  • 空间复杂度:(看置顶评论)是 O(1),不是

最后

这是我们「剑指 の 精选」系列文章的第 No.57 篇,系列开始于 2021/07/01。

该系列会将牛客网「剑指 Offer」中比较经典而又不过时的题目都讲一遍。

在提供追求「证明」&「思路」的同时,提供最为简洁的代码。

欢迎关注,交个朋友 (`・ω・´)

图片

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
用C++求二叉树果并打印可以使用递归方法实现,具体步骤如下: 1. 如果当前点为空,返回。 2. 递归遍左子树。 3. 输出当前点。 4. 递归遍右子树。 的最后一个点即为二叉树的最右下角点,可以在递归遍时记录最后一个点。 的第n个点可以在递归遍时使用一个计数器来记录当前是第几个点,并在遍到第n个点时输出。 以下是示例代码: ```c++ #include <iostream> using namespace std; // 二叉树点定义 struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode(int x) : val(x), left(NULL), right(NULL) {} }; void inorderTraversal(TreeNode* root) { if (root == NULL) { return; } inorderTraversal(root->left); cout << root->val << " "; inorderTraversal(root->right); } TreeNode* getInorderLastNode(TreeNode* root) { if (root == NULL) { return NULL; } while (root->right != NULL) { root = root->right; } return root; } int getInorderNthNode(TreeNode* root, int n, int& count) { if (root == NULL) { return -1; } int res = getInorderNthNode(root->left, n, count); if (count == n) { return root->val; } count++; if (res != -1) { return res; } return getInorderNthNode(root->right, n, count); } int main() { // 构建一个二叉树 TreeNode* root = new TreeNode(1); root->left = new TreeNode(2); root->right = new TreeNode(3); root->left->left = new TreeNode(4); root->left->right = new TreeNode(5); // inorderTraversal(root); cout << endl; // 的最后一个点 TreeNode* lastNode = getInorderLastNode(root); cout << lastNode->val << endl; // 的第n个点 int n = 3; int count = 1; int nthNode = getInorderNthNode(root, n, count); cout << nthNode << endl; return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值