【LeetCode】114. 二叉树展开为链表

leetcode

 

解题思路

  1. ​​问题本质​​:将二叉树原地展开为单链表,要求链表顺序与二叉树前序遍历顺序一致,且仅使用right指针连接
  2. ​​核心算法​​:采用​​Morris遍历​​思想实现O(1)空间复杂度
  3. ​​核心操作​​:
    • 对于每个节点,若存在左子树:
      • 找到左子树的最右节点(前驱节点)
      • 将原右子树接到最右节点的right指针
      • 将左子树移到右子树位置
      • 左指针置空
    • 移动到下一个右子节点继续处理

关键步骤

代码实现

type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

func flatten(root *TreeNode) {
    curr := root
    for curr != nil {
        if curr.Left != nil {
            // 1. 找左子树的最右节点
            predecessor := curr.Left
            for predecessor.Right != nil {
                predecessor = predecessor.Right
            }

            // 2. 连接原右子树
            predecessor.Right = curr.Right

            // 3. 左子树移到右侧
            curr.Right = curr.Left
            curr.Left = nil
        }
        // 4. 处理下一个节点
        curr = curr.Right
    }
}

代码解析

  1. ​​初始化​​:curr指针从根节点开始遍历
  2. ​​左子树处理​​(第12-23行):
    • 定位左子树最右节点(前驱节点)
    • 将当前节点的右子树接到前驱节点的Right
    • 左子树移到右侧(第22行),左指针置空
  3. ​​节点移动​​:沿Right指针移动(此时左子树已移到右侧)
  4. ​​终止条件​​:currnil时结束循环

示例测试

// 辅助函数:打印链表
func printList(root *TreeNode) {
    if root == nil {
        fmt.Println("[]")
        return
    }
    curr := root
    for curr != nil {
        fmt.Printf("%d", curr.Val)
        if curr.Right != nil {
            fmt.Print("->")
        }
        curr = curr.Right
    }
    fmt.Println()
}

func main() {
    // 示例1: [1,2,5,3,4,null,6]
    root := &TreeNode{1,
        &TreeNode{2,
            &TreeNode{3, nil, nil},
            &TreeNode{4, nil, nil},
        },
        &TreeNode{5,
            nil,
            &TreeNode{6, nil, nil},
        },
    }
    flatten(root)
    printList(root) // 输出: 1->2->3->4->5->6

    // 示例2: 空树
    var root2 *TreeNode
    flatten(root2)
    printList(root2) // 输出: []

    // 示例3: 单节点
    root3 := &TreeNode{0, nil, nil}
    flatten(root3)
    printList(root3) // 输出: 0
}

复杂度分析

指标说明
​​时间复杂度​​ O(n) 每个节点被访问2次(当前节点+前驱查找)
​​空间复杂度​​ O(1) 仅使用常数级别额外空间

关键点

  1. ​​Morris遍历核心​​:
    • 利用树中空闲指针(前驱节点的Right)存储信息
    • 避免递归栈或显式栈的空间开销
  2. ​​连接顺序​​:
    • 先连接原右子树到前驱节点
    • 再移动左子树到右侧
  3. ​​终止条件​​:
    • curr.Rightnil且无左子树时结束
  4. ​​原地算法​​:
    • 直接修改节点指针,不创建新节点
    • 符合进阶要求(O(1)额外空间)
posted @ 2025-06-07 14:53  云隙之间  阅读(25)  评论(0)    收藏  举报