Bootstrap

每日一题:填充二叉树节点的Next指针——从完美二叉树到任意二叉树

简介

在二叉树中,每个节点除了有左子节点和右子节点外,还可以有一个 next 指针,指向同一层中右侧的节点。填充 next 指针是一个经典的算法问题,常见于面试和编程竞赛中。本文将详细探讨如何在不同类型的二叉树中填充 next 指针,并分析其时间复杂度和空间复杂度。题目来源:牛客网


问题描述

给定一个二叉树,填充所有节点的 next 指针,使其指向同一层中右侧的节点。如果右侧没有节点,则 next 指针应设置为 NULL。初始时,所有 next 指针都为 NULL

二叉树节点定义

struct TreeLinkNode {
    TreeLinkNode *left;
    TreeLinkNode *right;
    TreeLinkNode *next;
};

问题分类

  1. 完美二叉树

    • 所有叶子节点都位于同一层。
    • 每个父节点都有两个子节点。
      在这里插入图片描述
  2. 任意二叉树

    • 节点可能只有一个子节点,或者没有子节点。
    • 叶子节点不一定位于同一层。
      在这里插入图片描述

思路解析

完美二叉树的解法

对于完美二叉树,我们可以利用其结构特点,逐层遍历并连接 next 指针。具体步骤如下:

  1. 逐层遍历

    • 从根节点开始,逐层向下遍历。
    • 对于每一层,从左到右连接 next 指针。
  2. 连接 next 指针

    • 对于每个节点,将其左子节点的 next 指向右子节点。
    • 如果当前节点有 next 节点,则将其右子节点的 next 指向 next 节点的左子节点。
代码实现
class Solution {
public:
    void connect(TreeLinkNode *root) {
        if (!root) return;
        TreeLinkNode *p = root, *q;
        while (p->left) {
            q = p;
            while (q) {
                q->left->next = q->right;
                if (q->next)
                    q->right->next = q->next->left;
                q = q->next;
            }
            p = p->left;
        }
    }
};
复杂度分析
  • 时间复杂度:O(N),其中 N 是节点数。每个节点只被访问一次。
  • 空间复杂度:O(1),只使用了常量级的额外空间。

任意二叉树的解法

对于任意二叉树,由于节点的子节点可能不完整,我们需要更通用的方法来连接 next 指针。具体步骤如下:

  1. 逐层遍历

    • 从根节点开始,逐层向下遍历。
    • 对于每一层,从左到右连接 next 指针。
  2. 连接 next 指针

    • 对于每个节点,根据其子节点情况连接 next 指针。
    • 如果当前节点有 next 节点,则将其子节点的 next 指向 next 节点的子节点。
代码实现
class Solution {
public:
    void connect(TreeLinkNode *root) {
        TreeLinkNode *p = root, *q;
        if (!p) return;
        while (p) {
            q = p;
            while (q) {
                int ret = abcd(q);
                switch (ret) {
                    case 1:
                        q->left->next = q->right;
                        if (q->next) {
                            q->right->next = getNextChild(q->next);
                        }
                        break;
                    case 2:
                        if (q->next) {
                            q->left->next = getNextChild(q->next);
                        }
                        break;
                    case 3:
                        if (q->next) {
                            q->right->next = getNextChild(q->next);
                        }
                        break;
                    case 4:
                        break;
                }
                q = q->next;
            }
            p = getNextChild(p);
        }
    }

    // 获取下一个有子节点的节点
    TreeLinkNode* getNextChild(TreeLinkNode* node) {
        while (node) {
            if (node->left) return node->left;
            if (node->right) return node->right;
            node = node->next;
        }
        return nullptr;
    }

    // 判断节点的子节点情况
    int abcd(TreeLinkNode *p) {
        if (p->left) {
            if (p->right) {
                return 1;
            } else {
                return 2;
            }
        } else {
            if (p->right) {
                return 3;
            } else {
                return 4;
            }
        }
    }
};
复杂度分析
  • 时间复杂度:O(N),其中 N 是节点数。每个节点只被访问一次。
  • 空间复杂度:O(1),只使用了常量级的额外空间。

语法介绍

重要语法点

语法点说明
switch-case 语句用于多分支选择,根据表达式的值执行不同的代码块。
while 循环用于重复执行代码块,直到条件不满足。
if-else 语句用于条件判断,根据条件执行不同的代码块。
return 语句用于从函数中返回值或结束函数执行。
nullptr表示空指针,用于初始化或比较指针是否为空。

扩展思考

1. 递归解法

对于完美二叉树,我们也可以使用递归的方法来连接 next 指针。递归解法的核心思想是:

  1. 递归连接子节点
    • 对于每个节点,递归连接其左子节点和右子节点的 next 指针。
    • 如果当前节点有 next 节点,则将其右子节点的 next 指向 next 节点的左子节点。
代码实现
class Solution {
public:
    void connect(TreeLinkNode *root) {
        if (!root) return; // 如果当前节点为空,直接返回
        if (root->left) {  // 如果当前节点有左子节点
            root->left->next = root->right; // 左子节点的 next 指向右子节点
            if (root->next) { // 如果当前节点有 next 节点
                root->right->next = root->next->left; // 右子节点的 next 指向 next 节点的左子节点
            }
        }
        connect(root->left);  // 递归处理左子树
        connect(root->right); // 递归处理右子树
    }
};
复杂度分析
  • 时间复杂度:O(N),其中 N 是节点数。每个节点只被访问一次。
  • 空间复杂度:O(H),其中 H 是树的高度。递归调用栈的深度为树的高度。

2. 层次遍历解法

对于任意二叉树,我们还可以使用层次遍历的方法来连接 next 指针。层次遍历解法的核心思想是:

  1. 使用队列逐层遍历
    • 将每一层的节点加入队列,并连接 next 指针。
    • 对于每一层,从左到右连接 next 指针。
代码实现
class Solution {
public:
    void connect(TreeLinkNode *root) {
        if (!root) return; // 如果根节点为空,直接返回

        // 使用队列进行层次遍历(BFS)
        queue<TreeLinkNode*> q;
        q.push(root); // 将根节点加入队列

        while (!q.empty()) { // 当队列不为空时,继续遍历
            int levelSize = q.size(); // 获取当前层的节点数
            TreeLinkNode* prev = nullptr; // 用于记录当前层的前一个节点

            // 遍历当前层的所有节点
            for (int i = 0; i < levelSize; i++) {
                TreeLinkNode* curr = q.front(); // 取出队列中的第一个节点
                q.pop(); // 将该节点从队列中移除

                // 如果 prev 不为空,将 prev 的 next 指向当前节点
                if (prev) {
                    prev->next = curr;
                }
                prev = curr; // 更新 prev 为当前节点

                // 将当前节点的左子节点加入队列(如果存在)
                if (curr->left) q.push(curr->left);
                // 将当前节点的右子节点加入队列(如果存在)
                if (curr->right) q.push(curr->right);
            }

            // 当前层遍历结束后,将最后一个节点的 next 置为 NULL
            if (prev) {
                prev->next = nullptr;
            }
        }
    }
};
复杂度分析
  • 时间复杂度:O(N),其中 N 是节点数。每个节点只被访问一次。
  • 空间复杂度:O(M),其中 M 是树的最大宽度。队列中最多存储一层的节点。

总结

本文详细探讨了如何在完美二叉树和任意二叉树中填充 next 指针,并提供了多种解法。通过分析不同解法的时间复杂度和空间复杂度,我们可以根据具体问题选择合适的算法。希望本文能帮助你更好地理解二叉树的相关算法,并在实际编程中灵活运用。


关于C++指针,更多内容请参考:C++ 指针大全:从基础到进阶,一篇快速上手!
关于二叉树深度探索,更多内容请参考:C++二叉树深度探索:DFS函数详解与应用实例
关注专栏:每日一题:C++ LeetCode精讲,和我一起进步


点个赞吧~,期待你的关注
图片名称

;