返回

二叉树展露拳脚:化身单链表,纵横解题思路

见解分享

引言:

在浩瀚的算法海洋中,二叉树的展开宛如一次技艺高超的舞蹈。它将原本交错繁杂的树枝,化作一串优雅的单链表,在数据结构的舞台上谱写出和谐的旋律。这篇文章将带领我们深入剖析这一算法的巧妙之处,领略其精妙的解题思路。

1. 解题策略:先序遍历的妙用

二叉树的展开本质上就是将一棵树的节点按先序遍历的顺序连接起来。先序遍历的步骤如下:

  1. 访问根节点。
  2. 遍历左子树。
  3. 遍历右子树。

我们不妨以一棵简单的二叉树为例,如下所示:

    1
   / \
  2   5
 / \   \
3   4   6

按照先序遍历,节点的访问顺序为:1 -> 2 -> 3 -> 4 -> 5 -> 6。

2. 代码实现:

基于先序遍历的思路,我们可以编写出如下代码:

public void flatten(TreeNode root) {
    if (root == null) {
        return;
    }
    // 递归遍历左子树
    flatten(root.left);
    // 递归遍历右子树
    flatten(root.right);
    // 将左子树接到右子树后面
    root.right = root.left;
    // 将左子树置为null,避免重复遍历
    root.left = null;
}

在这个代码中,我们通过递归的方式,不断地将左右子树展开为单链表,并连接到当前节点后面。最终,整个二叉树就被展平为一个单链表。

3. 关键技巧:利用全局变量

在实现代码的过程中,我们会遇到一个问题:当我们遍历到某个节点时,它的左子树已被展平,此时右子树的连接点应该指向左子树的最后一个节点。但是,我们无法直接访问左子树的最后一个节点。

为了解决这个问题,我们可以引入一个全局变量prev,它始终指向左子树的最后一个节点。在展开右子树之前,我们将root.right指向prev,从而实现左右子树的连接。

public void flatten(TreeNode root) {
    ...
    // 保存左子树最后一个节点的引用
    TreeNode prev = null;
    if (root.left != null) {
        flatten(root.left);
        prev = root.left;
        while (prev.right != null) {
            prev = prev.right;
        }
    }
    ...
    root.right = prev;
    ...
}

4. 拓展思考:空间复杂度优化

上面的代码中,我们使用了一个全局变量prev来存储左子树的最后一个节点,这会导致空间复杂度达到O(n),其中n为二叉树的节点数。我们可以通过修改代码,将空间复杂度优化到O(1)。

public void flatten(TreeNode root) {
    ...
    TreeNode prev = root;
    if (root.left != null) {
        flatten(root.left);
        prev = root.left;
        while (prev.right != null) {
            prev = prev.right;
        }
        prev.right = root.right;
    }
    root.right = root.left;
    root.left = null;
    ...
}

在这个优化后的代码中,我们不再使用全局变量,而是将prev变量指向当前节点的前一个节点。这样,在展开右子树之前,我们只需要将当前节点的右指针指向prev即可。

结语:

二叉树展开算法巧妙地利用了先序遍历的思想,将一棵树展平为一个单链表。通过引入全局变量和优化空间复杂度等技巧,我们可以进一步提升算法的性能。理解了这一算法的精妙之处,我们不仅能够解决实际问题,还能提升自己的算法思维能力,在数据结构和算法的领域中游刃有余。