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)),原地修改,不需要额外空间。

优劣势对比

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

应用示例

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

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


相关文章
|
3天前
|
算法 Java
[Java·算法·简单] LeetCode 283. 移动零
[Java·算法·简单] LeetCode 283. 移动零
10 2
|
3天前
|
算法 Java
[Java·算法·中等] LeetCode21. 合并两个有序链表
[Java·算法·中等] LeetCode21. 合并两个有序链表
10 2
|
6天前
|
存储 算法 调度
力扣中级算法(Python)
力扣中级算法(Python)
|
6天前
|
算法 C++
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题-2
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题
|
6天前
|
算法 C++
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题-1
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题
|
6天前
|
Java Python
二刷力扣--链表
二刷力扣--链表
|
1天前
|
机器学习/深度学习 算法 数据可视化
m基于PSO-LSTM粒子群优化长短记忆网络的电力负荷数据预测算法matlab仿真
在MATLAB 2022a中,应用PSO优化的LSTM模型提升了电力负荷预测效果。优化前预测波动大,优化后预测更稳定。PSO借鉴群体智能,寻找LSTM超参数(如学习率、隐藏层大小)的最优组合,以最小化误差。LSTM通过门控机制处理序列数据。代码显示了模型训练、预测及误差可视化过程。经过优化,模型性能得到改善。
15 6
|
1天前
|
算法 调度
基于变异混合蛙跳算法的车间调度最优化matlab仿真,可以任意调整工件数和机器数,输出甘特图
**摘要:** 实现变异混合蛙跳算法的MATLAB2022a版车间调度优化程序,支持动态调整工件和机器数,输出甘特图。核心算法结合SFLA与变异策略,解决Job-Shop Scheduling Problem,最小化总完成时间。SFLA模拟蛙群行为,分组进行局部搜索和全局信息交换。变异策略增强全局探索,避免局部最优。程序初始化随机解,按规则更新,经多次迭代和信息交换后终止。
|
12天前
|
算法
基于GA-PSO遗传粒子群混合优化算法的VRPTW问题求解matlab仿真
摘要: 本文介绍了考虑时间窗的车辆路径问题(VRPTW),在MATLAB2022a中进行测试。VRPTW涉及车辆从配送中心出发,服务客户并返回,需在指定时间窗内完成且满足车辆容量限制,目标是最小化总行驶成本。文章探讨了遗传算法(GA)和粒子群优化(PSO)的基本原理及其在VRPTW中的应用,包括编码、适应度函数、选择、交叉、变异等步骤。同时,提出了动态惯性权重、精英策略、邻域搜索、多种群和启发式信息等优化策略,以应对时间窗限制并提升算法性能。
|
6天前
|
算法 JavaScript 决策智能
基于禁忌搜索算法的TSP路径规划matlab仿真
**摘要:** 使用禁忌搜索算法解决旅行商问题(TSP),在MATLAB2022a中实现路径规划,显示优化曲线与路线图。TSP寻找最短城市访问路径,算法通过避免局部最优,利用禁忌列表不断调整顺序。关键步骤包括初始路径选择、邻域搜索、解评估、选择及禁忌列表更新。过程示意图展示搜索效果。