[路飞]_前端算法第一零七弹-扁平化多级双向链表

406 阅读3分钟

「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战

多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。

给你位于列表第一级的头节点,请你扁平化列表,使所有结点出现在单级双链表中。

示例 1:

输入:head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
输出:[1,2,3,7,8,11,12,9,10,4,5,6]
解释:

输入的多级列表如下图所示:

图片.png

扁平化后的链表如下图:

图片.png

示例 2:

输入:head = [1,2,null,3]
输出:[1,3,2]
解释:

输入的多级列表如下图所示:

  1---2---NULL
  |
  3---NULL

示例 3:

输入:head = []
输出:[]

如何表示测试用例中的多级链表?

示例 1 为例:

 1---2---3---4---5---6--NULL
         |
         7---8---9---10--NULL
             |
             11--12--NULL

序列化其中的每一级之后:

[1,2,3,4,5,6,null]
[7,8,9,10,null]
[11,12,null]

为了将每一级都序列化到一起,我们需要每一级中添加值为 null 的元素,以表示没有节点连接到上一级的上级节点。

[1,2,3,4,5,6,null]
[null,null,7,8,9,10,null]
[null,11,12,null]

合并所有序列化结果,并去除末尾的 null 。

[1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]

深度优先搜索

当我们遍历到某个节点 nodenode 时,如果它的 childchild 成员不为空,那么我们需要将 childchild 指向的链表结构进行扁平化,并且插入 nodenodenodenode 的下一个节点之间。

因此,我们在遇到 childchild 成员不为空的节点时,就要先去处理 childchild 指向的链表结构,这就是一个「深度优先搜索」的过程。当我们完成了对 childchild 指向的链表结构的扁平化之后,就可以「回溯」到 nodenode 节点。

为了能够将扁平化的链表插入 nodenodenodenode 的下一个节点之间,我们需要知道扁平化的链表的最后一个节点 lastlast,随后进行如下的三步操作:

  • nodenodenodenode 的下一个节点 nextnext 断开:
  • nodenodechildchild 相连;
  • lastlastnextnext 相连。

这样一来,我们就可以将扁平化的链表成功地插入。

图片.png

在深度优先搜索完成后,我们返回给定的首节点即可。

细节

需要注意的是,nodenode 可能没有下一个节点,即 nextnext 为空。此时,我们只需进行第二步操作。

此外,在插入扁平化的链表后,我们需要将 nodenodechildchild 成员置为空。

var flatten = function(head) {
    const dfs = (node) => {
        let cur = node;
        // 记录链表的最后一个节点
        let last = null;

        while (cur) {
            let next = cur.next;
            //  如果有子节点,那么首先处理子节点
            if (cur.child) {
                const childLast = dfs(cur.child);

                next = cur.next;
                //  将 node 与 child 相连
                cur.next = cur.child;
                cur.child.prev = cur;

                //  如果 next 不为空,就将 last 与 next 相连
                if (next != null) {
                    childLast.next = next;
                    next.prev = childLast;
                }

                // 将 child 置为空
                cur.child = null;
                last = childLast;
            } else {
                last = cur;
            }
            cur = next;

        }
        return last;
    }

    dfs(head);
    return head;
};

复杂度分析

  • 时间复杂度:O(n)O(n),其中 n 是链表中的节点个数。
  • 空间复杂度:O(n)O(n)。上述代码中使用的空间为深度优先搜索中的栈空间,如果给定的链表的「深度」为 d,那么空间复杂度为 O(d)O(d)。在最换情况下,链表中的每个节点的 next\textit{next}next 都为空,且除了最后一个节点外,每个节点的 childchild 都不为空,整个链表的深度为 n,因此时间复杂度为 O(n)O(n)