leetcode
leetcode 651 ~ 700
扁平化多级双向链表

扁平化多级双向链表

难度:

标签:

题目描述

你会得到一个双链表,其中包含的节点有一个下一个指针、一个前一个指针和一个额外的 子指针 。这个子指针可能指向一个单独的双向链表,也包含这些特殊的节点。这些子列表可以有一个或多个自己的子列表,以此类推,以生成如下面的示例所示的 多层数据结构

给定链表的头节点 head ,将链表 扁平化 ,以便所有节点都出现在单层双链表中。让 curr 是一个带有子列表的节点。子列表中的节点应该出现在扁平化列表中的 curr 之后 和 curr.next 之前

返回 扁平列表的 head 。列表中的节点必须将其 所有 子指针设置为 null 。

 

示例 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]
解释:输入的多级列表如上图所示。
扁平化后的链表如下图:

示例 2:

输入:head = [1,2,null,3]
输出:[1,3,2]
解释:输入的多级列表如上图所示。
扁平化后的链表如下图:

示例 3:

输入:head = []
输出:[]
说明:输入中可能存在空列表。

 

提示:

  • 节点数目不超过 1000
  • 1 <= Node.val <= 105

 

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

示例 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]

代码结果

运行时间: 32 ms, 内存: 16.1 MB


/*
 * Problem: Flatten a multilevel doubly linked list using Java Streams.
 * 
 * Approach:
 * 1. Convert the linked list into a list of nodes, including child nodes.
 * 2. Flatten the list of nodes into a single list using a stream.
 * 3. Reconstruct the linked list from the flattened list.
 */

import java.util.*;
import java.util.stream.*;

class Node {
    int val;
    Node next;
    Node prev;
    Node child;

    Node(int val) {
        this.val = val;
    }
}

public class Solution {
    public Node flatten(Node head) {
        if (head == null) return null;

        // Create a list of all nodes, including child nodes
        List<Node> nodes = new ArrayList<>();
        flattenList(head, nodes);

        // Flatten the list using streams and set the child pointers to null
        List<Node> flattenedList = nodes.stream()
            .flatMap(n -> n.child == null ? Stream.of(n) : Stream.concat(Stream.of(n), flattenChild(n.child).stream()))
            .peek(n -> n.child = null)
            .collect(Collectors.toList());

        // Reconstruct the linked list
        Node dummy = new Node(0);
        Node curr = dummy;
        for (Node node : flattenedList) {
            curr.next = node;
            node.prev = curr;
            curr = curr.next;
        }
        dummy.next.prev = null;
        return dummy.next;
    }

    private void flattenList(Node head, List<Node> nodes) {
        Node curr = head;
        while (curr != null) {
            nodes.add(curr);
            if (curr.child != null) flattenList(curr.child, nodes);
            curr = curr.next;
        }
    }

    private List<Node> flattenChild(Node child) {
        List<Node> childList = new ArrayList<>();
        flattenList(child, childList);
        return childList;
    }
}

解释

方法:

该题解采用了迭代的方式来处理多级双向链表的扁平化。首先,创建一个虚拟头节点 `dummy1` 来简化边界条件的处理,并使得返回扁平化链表时更为方便。然后,使用一个指针 `cur` 从虚拟头节点开始遍历原始链表。对于每个节点,检查是否存在子节点: 1. 如果当前节点 `cur` 有子节点,将其子节点插入到 `cur` 和 `cur.next` 之间,并更新相应的指针。 2. 如果 `cur` 没有子节点,则直接移动到下一个节点。 3. 在将子链表插入后,原始的 `cur.next` 节点(如果存在的话)会被暂存起来,并在处理完所有子链表后,接在当前链表的末尾。 这种方法确保了所有子链表被逐一插入到正确的位置,最终实现整个链表的扁平化。

时间复杂度:

O(n)

空间复杂度:

O(1)

代码细节讲解

🦆
在处理子链表时,你是如何确保所有的子链表都能正确地插入到它们相应的父节点之后的?
在处理子链表时,算法首先检查当前节点 `cur` 是否有子节点。如果有,它会将 `cur` 的子节点插入到 `cur` 和 `cur.next` 之间。这通过直接修改 `cur.next` 指向 `cur.child` 并同时更新 `cur.child.prev` 指向 `cur` 来完成。接着,原 `cur.next`(如果存在)会被暂存,并在子链表处理完后接回到链表的末端。这样确保了每个子链表都正确地插在其父节点之后。
🦆
对于多层子链表的情况,你的算法是否能够递归地扁平化所有子链表,直到没有更多的子链表存在?
是的,算法能够递归地处理多层子链表。这是通过在遍历链表时,每遇到一个有子节点的节点,就先处理该子链表,然后再继续处理原始链表的下一个节点。这个过程会一直重复,直到所有可能的子链表都被处理完毕,确保了即使是多层子链表也能被完全扁平化。
🦆
在重新连接原始的 `cur.next` 链表时,你如何处理可能出现的循环引用问题?
在重新连接原始的 `cur.next` 链表时,我确保在将子链表插入后,暂存原始的 `cur.next`。在处理完所有子链表后,我检查并确保 `cur.next` 是空(即当前链表的末尾),然后再将暂存的原始 `cur.next` 链表接回去,并更新相应的 `prev` 指针。这样的处理避免了循环引用的问题,因为我们通过逻辑保证每个节点只被访问一次,并且在连接时验证了链表的末端状态。

相关问题

二叉树展开为链表

给你二叉树的根结点 root ,请你将它展开为一个单链表:

  • 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null
  • 展开后的单链表应该与二叉树 先序遍历 顺序相同。

 

示例 1:

输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [0]
输出:[0]

 

提示:

  • 树中结点数在范围 [0, 2000]
  • -100 <= Node.val <= 100

 

进阶:你可以使用原地算法(O(1) 额外空间)展开这棵树吗?