LeetCode力扣第114题:多种算法实现 将二叉树展开为链表

简介: LeetCode力扣第114题:多种算法实现 将二叉树展开为链表

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

会一些的技术:数据分析、算法、SQL、大数据相关、python

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

作者专栏每日更新:

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

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

python源码解读

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

题目描述

给定一个二叉树,原地将它展开为一个单链表。例如,给定二叉树:

1
   / \
  2   5
 / \   \
3   4   6

展开后应该变为:

1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

方法一:递归展开

解题步骤

  1. 如果树为空,直接返回。
  2. 递归展开左子树和右子树。
  3. 将左子树插入到右子树的位置。
  4. 将原来的右子树接到当前右子树的末端。
  5. 考虑到展开后没有左子节点,将左子节点设置为None

代码示例

class Solution:
    def flatten(self, root):
        if not root:
            return
        self.flatten(root.left)
        self.flatten(root.right)
        # 左右子树已经被拉平成一条链表
        left = root.left
        right = root.right
        # 将左子树作为右子树
        root.left = None
        root.right = left
        # 找到当前右子树(原左子树)的末端并连接原右子树
        p = root
        while p.right:
            p = p.right
        p.right = right

方法一的改进主要可以从两个方面进行:优化递归效率和空间使用。虽然基本递归方法简单直观,但它可能导致不必要的栈空间消耗,尤其是在处理非常深的树时可能会导致栈溢出。以下是几种改进策略:

改进1: 尾递归优化

虽然Python默认不支持尾递归优化,但我们可以尽可能减少递归中不必要的操作,将必要的操作移至递归调用之前执行,减少递归调用栈的深度。

改进代码示例

class Solution:
    def flatten(self, root):
        def flattenTree(node):
            if not node:
                return None
            
            # 如果节点是叶子节点,直接返回
            if not node.left and not node.right:
                return node
            
            # 递归平展左子树
            leftTail = flattenTree(node.left)
            rightTail = flattenTree(node.right)
            
            # 将左子树插入到右子树的地方
            if leftTail:
                leftTail.right = node.right
                node.right = node.left
                node.left = None
            
            # 返回最后的尾部节点
            return rightTail if rightTail else leftTail
        
        flattenTree(root)
改进2: 减少递归深度

尽可能地在每个节点处理完毕后立即释放其左子树的引用,减少Python垃圾回收器的压力,并减少递归深度。

改进代码示例

class Solution:
    def flatten(self, root):
        # Helper function to perform flatten operation
        def flattenNode(node):
            if not node:
                return None
            # Flatten the left and right subtree
            left_last = flattenNode(node.left)
            right_last = flattenNode(node.right)
            # If there is a left subtree, weave it into the right subtree
            if left_last:
                left_last.right = node.right
                node.right = node.left
                node.left = None
            # The rightmost node is needed for linking to the parent node's right subtree
            return right_last if right_last else left_last
        flattenNode(root)

方法二:迭代展开

解题步骤

  1. 使用栈来辅助迭代过程。
  2. 每次取出栈顶元素,如果有右子节点先压栈,再压左子节点。
  3. 修改每个节点的右指针指向下一个栈顶元素,左指针设置为None

代码示例

class Solution:
    def flatten(self, root):
        if not root:
            return
        
        stack = [root]
        prev = None
        
        while stack:
            curr = stack.pop()
            if prev:
                prev.right = curr
                prev.left = None
            if curr.right:
                stack.append(curr.right)
            if curr.left:
                stack.append(curr.left)
            prev = curr

方法三:寻找前驱节点

解题步骤

  1. 对于每个节点,如果左子节点不为空,找到左子树的最右节点(前驱节点)。
  2. 将原右子树接到前驱节点的右边。
  3. 将左子树移到右边,左子树置为空。
  4. 继续处理链表中的下一个节点。

代码示例

class Solution:
    def flatten(self, root):
        curr = root
        while curr:
            if curr.left:
                pre = curr.left
                while pre.right:
                    pre = pre.right
                pre.right = curr.right
                curr.right = curr.left
                curr.left = None
            curr = curr.right

算法分析

  • 时间复杂度
  • 递归展开:(O(n)),每个节点访问一次。
  • 迭代展开:(O(n)),每个节点访问一次。
  • 寻找前驱节点:(O(n)),每个节点访问一次。
  • 空间复杂度
  • 递归展开:(O(n)),递归栈的空间。
  • 迭代展开:(O(n)),使用了额外的栈。
  • 寻找前驱节点:(O(1)),原地修改,不需要额外空间。

优劣势对比

  • 递归展开
  • 优点:实现简单直观。
  • 缺点:需要额外的栈空间来处理递归。
  • 迭代展开
  • 优点:避免了递归可能导致的栈溢出。
  • 缺点:需要使用栈来存储节点。
  • 寻找前驱节点
  • 优点:不需要额外空间,空间复杂度最优。
  • 缺点:代码逻辑相对复杂,需要处理更多的指针操作。

应用示例

这种技巧在处理需要将树结构线性化处理的场景非常有用,例如在图形界面开发中,需要按特定顺序访问或显示节点信息,或者在需要频繁查找、更新结点而又不频繁修改树结构的数据库和文件系统中。

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


相关文章
|
2月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
7月前
|
Go 开发者 索引
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣33 / 81/ 153/154)(Go语言版)
本文深入探讨了LeetCode中四道关于「搜索旋转排序数组」的经典题目,涵盖了无重复和有重复元素的情况。通过二分查找的变形应用,文章详细解析了每道题的解题思路和Go语言实现代码。关键点包括判断有序区间、处理重复元素以及如何缩小搜索范围。文章还总结了各题的异同,并推荐了类似题目,帮助读者全面掌握二分查找在旋转数组中的应用。无论是初学者还是有经验的开发者,都能从中获得实用的解题技巧和代码实现方法。
316 14
|
8月前
|
算法 Go
【LeetCode 热题100】深入理解二叉树结构变化与路径特性(力扣104 / 226 / 114 / 543)(Go语言版)
本博客深入探讨二叉树的深度计算、结构变换与路径分析,涵盖四道经典题目:104(最大深度)、226(翻转二叉树)、114(展开为链表)和543(二叉树直径)。通过递归与遍历策略(前序、后序等),解析每题的核心思路与实现方法。结合代码示例(Go语言),帮助读者掌握二叉树相关算法的精髓。下一讲将聚焦二叉树构造问题,欢迎持续关注!
198 10
|
8月前
|
存储 算法 数据可视化
【二叉树遍历入门:从中序遍历到层序与右视图】【LeetCode 热题100】94:二叉树的中序遍历、102:二叉树的层序遍历、199:二叉树的右视图(详细解析)(Go语言版)
本文详细解析了二叉树的三种经典遍历方式:中序遍历(94题)、层序遍历(102题)和右视图(199题)。通过递归与迭代实现中序遍历,深入理解深度优先搜索(DFS);借助队列完成层序遍历和右视图,掌握广度优先搜索(BFS)。文章对比DFS与BFS的思维方式,总结不同遍历的应用场景,为后续构造树结构奠定基础。
393 10
|
8月前
|
Go
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣437 / 236 )(Go语言版)
本文深入探讨二叉树中路径与祖先问题,涵盖两道经典题目:LeetCode 437(路径总和 III)和236(最近公共祖先)。对于路径总和 III,文章分析了双递归暴力解法与前缀和优化方法,后者通过哈希表记录路径和,将时间复杂度从O(n²)降至O(n)。在最近公共祖先问题中,采用后序遍历递归查找,利用“自底向上”的思路确定最近公共祖先节点。文中详细解析代码实现与核心要点,帮助读者掌握深度追踪技巧,理解树结构中路径与节点关系的本质。这类问题在面试中高频出现,掌握其解法意义重大。
192 4
|
8月前
|
Go 索引 Perl
【LeetCode 热题100】【二叉树构造题精讲:前序 + 中序建树 & 有序数组构造 BST】(详细解析)(Go语言版)
本文详细解析了二叉树构造的两类经典问题:通过前序与中序遍历重建二叉树(LeetCode 105),以及将有序数组转化为平衡二叉搜索树(BST,LeetCode 108)。文章从核心思路、递归解法到实现细节逐一拆解,强调通过索引控制子树范围以优化性能,并对比两题的不同构造逻辑。最后总结通用构造套路,提供进阶思考方向,帮助彻底掌握二叉树构造类题目。
436 9
|
8月前
|
算法 Go
【LeetCode 热题100】23:合并 K 个升序链表(详细解析)(Go语言版)
本文详细解析了 LeetCode 热题 23——合并 K 个升序链表的两种解法:优先队列(最小堆)和分治合并。题目要求将多个已排序链表合并为一个升序链表。最小堆方法通过维护节点优先级快速选择最小值,;分治合并则采用归并思想两两合并链表。文章提供了 Go 语言实现代码,并对比分析两种方法的适用场景,帮助读者深入理解链表操作与算法设计。
288 10
|
12月前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
286 4
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
171 2

热门文章

最新文章