LeetCode 题目 116:填充每个节点的下一个右侧节点指针

简介: LeetCode 题目 116:填充每个节点的下一个右侧节点指针

作者介绍:10年大厂数据\经营分析经验,现任字节跳动数据部门负责人

会一些的技术:数据分析、算法、SQL、大数据相关、python,欢迎探讨交流

欢迎加入社区:码上找工作

作者专栏每日更新:

LeetCode解锁1000题: 打怪升级之旅

python数据分析可视化:企业实战案例

漫画版算法详解

python源码解读

程序员必备的数学知识与应用

题目描述

给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树的定义如下:

class Node:
    def __init__(self, val=0, left=None, right=None, next=None):
        self.val = val
        self.left = left
        self.right = right
        self.next = next

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

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

方法一:层序遍历(BFS)

解题步骤:
  1. 使用队列进行层序遍历。
  2. 对于每一层的节点,通过队列的长度来确定节点的数量。
  3. 遍历当前层的节点,将每个节点的 next 指向队列中的下一个节点。
  4. 最后一个节点的 next 指向 None
Python 代码示例
from collections import deque
def connect(root):
    """
    使用层序遍历来填充每个节点的下一个右侧节点指针。
    
    参数:
    root (Node): 完美二叉树的根节点。
    
    返回:
    Node: 修改后的树的根节点。
    """
    # 如果根节点为空,则直接返回None
    if not root:
        return None
    # 初始化队列,并将根节点加入队列
    queue = deque([root])
    # 当队列不为空时,进行层序遍历
    while queue:
        # 获取当前层的节点数量
        size = len(queue)
        # 遍历当前层的每个节点
        for i in range(size):
            # 从队列中取出一个节点
            node = queue.popleft()
            # 如果不是当前层的最后一个节点,将当前节点的next指向队列中的下一个节点
            if i < size - 1:
                node.next = queue[0]
            # 如果当前节点有左子节点,将左子节点加入队列
            if node.left:
                queue.append(node.left)
            # 如果当前节点有右子节点,将右子节点加入队列
            if node.right:
                queue.append(node.right)
    # 返回根节点
    return root

方法一利用层序遍历(BFS)来填充每个节点的下一个右侧节点指针,下面通过 ASCII 图形来说明这个方法的工作过程。

考虑一个完美二叉树如下所示:

1
       / \
      2   3
     / \ / \
    4  5 6  7
算法图解

我们使用队列来按层级遍历这棵树,并设置每个节点的 next 指针。以下是各个步骤的图解说明:

初始状态
  1. 初始化队列以包含根节点。
队列: [1]
第一层处理
  1. 取出节点 1,发现它有左右子节点 2 和 3,将这两个节点加入队列。
队列: [2, 3]
next指针连接: 1 -> None
  1. 设置节点 1 的 next 指向 None(因为它是第一层的唯一节点)。
第二层处理
  1. 取出节点 2,由于它不是队列中的最后一个节点,设置 next 指向队列中的下一个节点(节点 3)。同时将节点 2 的子节点 4 和 5 加入队列。
队列: [3, 4, 5]
next指针连接: 2 -> 3
  1. 取出节点 3,设置 next 指向 None(因为它是当前层的最后一个节点),将节点 3 的子节点 6 和 7 加入队列。
队列: [4, 5, 6, 7]
next指针连接: 3 -> None
第三层处理
  1. 处理节点 4, 5, 6, 7 类似,按顺序设置 next 指针:
队列: [5, 6, 7]
next指针连接: 4 -> 5
队列: [6, 7]
next指针连接: 5 -> 6
队列: [7]
next指针连接: 6 -> 7
队列: []
next指针连接: 7 -> None

在每一步中,我们从队列中移除一个节点,如果该节点不是当前层的最后一个节点,则将其 next 指针设置指向队列中的下一个节点。这样,每一层的节点都通过 next 指针连成一条链。当处理完当前层的所有节点后,队列中就包含了下一层的所有节点,重复以上步骤直到队列为空。这样的遍历确保了每个节点的 next 指针正确地指向了其右侧的节点。

方法二:使用已建立的 next 指针

解题步骤:
  1. 从根节点开始,利用上一层的 next 指针来遍历和链接当前层的节点。
  2. 对于每个节点,链接其左子节点到右子节点,右子节点到下一个节点的左子节点(如果存在)。
Python 代码示例
def connect(root):
    """
    利用已建立的 next 指针来填充每个节点的下一个右侧节点指针。
    
    参数:
    root (Node): 完美二叉树的根节点。
    
    返回:
    Node: 修改后的树的根节点。
    """
    # 如果根节点为空,则直接返回None
    if not root:
        return None
    # 初始化leftmost指针,这个指针始终指向每一层的最左侧节点
    leftmost = root
    # 当左侧节点存在时,继续处理下一层
    while leftmost.left:
        # 使用head指针遍历当前层的节点,head初始指向当前层的最左侧节点
        head = leftmost
        while head:
            # 将当前节点的左子节点的next指向右子节点
            head.left.next = head.right
            # 如果当前节点的next不为空,将右子节点的next指向当前节点next的左子节点
            if head.next:
                head.right.next = head.next.left
            
            # 移动到当前层的下一个节点
            head = head.next
        
        # 移动到下一层的最左侧节点
        leftmost = leftmost.left
    # 返回根节点
    return root

方法三:递归法

解题步骤:
  1. 递归地连接每个节点的左子节点到其右子节点。
  2. 连接相邻子树的右子节点到左子节点。
Python 代码示例
def connect(root):
    if not root or not root.left:
        return root
    root.left.next = root.right
    if root.next:
        root.right.next = root.next.left
    connect(root.left)
    connect(root.right)
    return root

方法四:使用额外的节点追踪下一层的开始

解题步骤:
  1. 使用一个额外的节点 dummy 来追踪每一层的开始。
  2. 通过一个循环连接同一层的所有节点。
Python 代码示例
def connect(root):
    if not root:
        return None
    current = root
    while current:
        dummy = Node(0)
        tail = dummy
        while current:
            if current.left:
                tail.next = current.left
                tail = tail.next
            if current.right:
                tail.next = current.right
                tail = tail.next
            current = current.next
        current = dummy.next
    return root

方法五:迭代优化

解题步骤:
  1. 类似于方法二,但使用迭代而非递归。
  2. 遍历每一层,连接相应的节点。
Python 代码示例
def connect(root):
    if not root:
        return None
    node = root
    while node and node.left:
        next_level = node.left
        while node:
            node.left.next = node.right
            node.right.next =
 node.next.left if node.next else None
            node = node.next
        node = next_level
    return root

算法分析

  • 时间复杂度:所有方法都需要遍历每个节点,因此时间复杂度为 O(N),其中 N 是树中的节点数。
  • 空间复杂度
  • 方法一:O(N),需要额外的队列。
  • 方法二至五:O(1),只使用常数额外空间。

不同算法的优劣势对比

  • 层序遍历(方法一)直观且易于理解,但需要额外的空间来存储队列。
  • 使用已建立的 next 指针(方法二)是空间复杂度最优的解法,适合空间敏感的应用。
  • 递归法(方法三)代码简洁,但在非尾递归的编译器上可能导致栈溢出。
  • 使用额外节点(方法四)适合不熟悉指针操作的场景。
  • 迭代优化(方法五)提供了一个折中的解决方案,空间复杂度低,且较易于理解。

应用示例

这些方法可以用在需要层级遍历但希望节约空间的场景,如实时数据处理、游戏编程中的场景管理,或任何需要快速访问同一层级节点的数据结构设计中。


欢迎关注微信公众号 数据分析螺丝钉

相关文章
|
1月前
|
程序员 C语言
【C语言】LeetCode(力扣)上经典题目
【C语言】LeetCode(力扣)上经典题目
10_填充每个节点的下一个右侧节点指针
10_填充每个节点的下一个右侧节点指针
|
2月前
|
SQL Oracle 关系型数据库
CASE WHEN 语句的语法及示例,LeetCode 题目 “确认率” 练习
本文介绍了SQL中CASE语句的两种形式和语法,并通过LeetCode题目“确认率”的SQL查询示例展示了CASE语句在实际问题中的应用,解释了如何使用CASE语句计算特定条件的比率。
|
3月前
|
算法
LeetCode第12题目整数转罗马数字
该文章介绍了 LeetCode 第 12 题整数转罗马数字的解法,通过使用 TreeMap 按照整数从大到小排序,先使用大的罗马数字表示整数,再用小的,核心是先表示完大的罗马数字,想通此点该题较简单。
LeetCode第12题目整数转罗马数字
|
3月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
50 6
|
3月前
|
Python
【Leetcode刷题Python】138. 复制带随机指针的链表
LeetCode上题目“138. 复制带随机指针的链表”的Python解决方案,包括两种方法:一种是在每个节点后复制一个新节点然后再分离出来形成新链表;另一种是构建一个字典来跟踪原始节点与其副本之间的映射关系,从而处理新链表的构建。
20 1
|
3月前
|
算法
LeetCode第13题目罗马数字转整数
该文章介绍了 LeetCode 第 13 题罗马数字转整数的解法,通过从大到小解析罗马数字,根据罗马数字的特点,按照从大到小的顺序匹配罗马数字和整数的关系,从而解决该问题,同时强调要注意观察题目考查的知识点特征。
|
5月前
|
C语言
详解Leetcode中关于malloc模拟开辟二维数组问题,涉及二维数组的题目所给函数中的各个参数的解读
详解Leetcode中关于malloc模拟开辟二维数组问题,涉及二维数组的题目所给函数中的各个参数的解读
33 1
|
5月前
|
算法
【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素
【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素