原文链接: https://leetcode-cn.com/problems/flatten-a-multilevel-doubly-linked-list
英文原文
You are given a doubly linked list, which contains nodes that have a next pointer, a previous pointer, and an additional child pointer. This child pointer may or may not point to a separate doubly linked list, also containing these special nodes. These child lists may have one or more children of their own, and so on, to produce a multilevel data structure as shown in the example below.
Given the head
of the first level of the list, flatten the list so that all the nodes appear in a single-level, doubly linked list. Let curr
be a node with a child list. The nodes in the child list should appear after curr
and before curr.next
in the flattened list.
Return the head
of the flattened list. The nodes in the list must have all of their child pointers set to null
.
Example 1:
Input: head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12] Output: [1,2,3,7,8,11,12,9,10,4,5,6] Explanation: The multilevel linked list in the input is shown. After flattening the multilevel linked list it becomes:
Example 2:
Input: head = [1,2,null,3] Output: [1,3,2] Explanation: The multilevel linked list in the input is shown. After flattening the multilevel linked list it becomes:
Example 3:
Input: head = [] Output: [] Explanation: There could be empty list in the input.
Constraints:
- The number of Nodes will not exceed
1000
. 1 <= Node.val <= 105
How the multilevel linked list is represented in test cases:
We use the multilevel linked list from Example 1 above:
1---2---3---4---5---6--NULL | 7---8---9---10--NULL | 11--12--NULL
The serialization of each level is as follows:
[1,2,3,4,5,6,null] [7,8,9,10,null] [11,12,null]
To serialize all levels together, we will add nulls in each level to signify no node connects to the upper node of the previous level. The serialization becomes:
[1, 2, 3, 4, 5, 6, null] | [null, null, 7, 8, 9, 10, null] | [ null, 11, 12, null]
Merging the serialization of each level and removing trailing nulls we obtain:
[1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
中文题目
多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。
给你位于列表第一级的头节点,请你扁平化列表,使所有结点出现在单级双链表中。
示例 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] 解释: 输入的多级列表如下图所示: 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]
提示:
- 节点数目不超过 1000
1 <= Node.val <= 10^5
通过代码
高赞题解
递归
一道常规链表模拟题。
利用 flatten
函数本身的含义(将链表头为 $head$ 的链表进行扁平化,并将扁平化后的头结点进行返回),我们可以很容易写出递归版本。
为防止空节点等边界问题,起始时建立一个哨兵节点 $dummy$ 指向 $head$,然后利用 $head$ 指针从前往后处理链表:
- 当前节点 $head$ 没有 $child$ 节点:直接让指针后即可,即 $head = head.next$;
- 当前节点 $head$ 有 $child$ 节点:将 $head.child$ 传入
flatten
函数递归处理,拿到普遍化后的头结点 $chead$,然后将 $head$ 和 $chead$ 建立“相邻”关系(注意要先存起来原本的 $tmp = head.next$ 以及将 $head.child$ 置空),然后继续往后处理,直到扁平化的 $chead$ 链表的尾部,将其与 $tmp$ 建立“相邻”关系。
重复上述过程,直到整条链表被处理完。
代码:
class Solution {
public Node flatten(Node head) {
Node dummy = new Node(0);
dummy.next = head;
while (head != null) {
if (head.child == null) {
head = head.next;
} else {
Node tmp = head.next;
Node chead = flatten(head.child);
head.next = chead;
chead.prev = head;
head.child = null;
while (head.next != null) head = head.next;
head.next = tmp;
if (tmp != null) tmp.prev = head;
head = tmp;
}
}
return dummy.next;
}
}
- 时间复杂度:最坏情况下,每个节点会被访问 $h$ 次($h$ 为递归深度,最坏情况下 $h = n$)。整体复杂度为 $O(n^2)$
- 空间复杂度:最坏情况下所有节点都分布在
child
中,此时递归深度为 $n$。复杂度为 $O(n)$
递归(优化)
在上述解法中,由于我们直接使用 flatten
作为递归函数,导致递归处理 $head.child$ 后不得不再进行遍历来找当前层的“尾结点”,这导致算法复杂度为 $O(n^2)$。
一个可行的优化是,额外设计一个递归函数 dfs
用于返回扁平化后的链表“尾结点”,从而确保我们找尾结点的动作不会在每层发生。
代码:
class Solution {
public Node flatten(Node head) {
dfs(head);
return head;
}
Node dfs(Node head) {
Node last = head;
while (head != null) {
if (head.child == null) {
last = head;
head = head.next;
} else {
Node tmp = head.next;
Node childLast = dfs(head.child);
head.next = head.child;
head.child.prev = head;
head.child = null;
if (childLast != null) childLast.next = tmp;
if (tmp != null) tmp.prev = childLast;
last = head;
head = childLast;
}
}
return last;
}
}
- 时间复杂度:$O(n)$
- 空间复杂度:最坏情况下所有节点都分布在
child
中,此时递归深度为 $n$。复杂度为 $O(n)$
迭代
自然也能够使用迭代进行求解。
与「递归」不同的是,「迭代」是以“段”为单位进行扁平化,而「递归」是以深度(方向)进行扁平化,这就导致了两种方式对每个扁平节点的处理顺序不同。
已样例 $1$ 为 🌰。
递归的处理节点(新的 $next$ 指针的构建)顺序为:
迭代的处理节点(新的 $next$ 指针的构建)顺序为:
但由于链表本身不存在环,「迭代」的构建顺序发生调整,仍然可以确保每个节点被访问的次数为常数次。
代码:
class Solution {
public Node flatten(Node head) {
Node dummy = new Node(0);
dummy.next = head;
for (; head != null; head = head.next) {
if (head.child != null) {
Node tmp = head.next;
Node child = head.child;
head.next = child;
child.prev = head;
head.child = null;
Node last = head;
while (last.next != null) last = last.next;
last.next = tmp;
if (tmp != null) tmp.prev = last;
}
}
return dummy.next;
}
}
- 时间复杂度:可以发现,迭代写法的扁平化过程并不与遍历方向保持一致(以段为单位进行扁平化,而非像递归那样总是往遍历方向进行扁平化),但每个节点被访问的次数仍为常数次。复杂度为 $O(n)$
- 空间复杂度:$O(1)$
其他「链表」类的内容
可尝试加练如下「链表」类内容 🍭🍭🍭 :
题目 | 题解 | 难度 | 推荐指数 |
---|---|---|---|
2. 两数相加 | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
19. 删除链表的倒数第 N 个结点 | LeetCode 题解链接 | 中等 | 🤩🤩🤩🤩🤩 |
21. 合并两个有序链表 | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩🤩 |
23. 合并K个升序链表 | LeetCode 题解链接 | 困难 | 🤩🤩🤩 |
24. 两两交换链表中的节点 | LeetCode 题解链接 | 中等 | 🤩🤩🤩🤩 |
25. K 个一组翻转链表 | LeetCode 题解链接 | 困难 | 🤩🤩 |
61. 旋转链表 | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
83. 删除排序链表中的重复元素 | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩🤩 |
82. 删除排序链表中的重复元素 II | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩🤩 |
92. 反转链表 II | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
138. 复制带随机指针的链表 | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
160. 相交链表 | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩🤩 |
146. LRU 缓存机制 | LeetCode 题解链接 | 中等 | 🤩🤩🤩🤩🤩 |
203. 移除链表元素 | LeetCode 题解链接 | 简单 | 🤩🤩🤩 |
430. 扁平化多级双向链表 | LeetCode 题解链接 | 中等 | 🤩🤩🤩🤩🤩 |
460. LFU 缓存 | LeetCode 题解链接 | 困难 | 🤩🤩🤩🤩🤩 |
725. 分隔链表 | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩🤩 |
1600. 皇位继承顺序 | LeetCode 题解链接 | 中等 | 🤩🤩🤩 |
剑指 Offer 22. 链表中倒数第k个节点 | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩🤩 |
剑指 Offer 52. 两个链表的第一个公共节点 | LeetCode 题解链接 | 简单 | 🤩🤩🤩🤩🤩 |
注:以上目录整理来自 wiki,任何形式的转载引用请保留出处。
最后
如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (“▔□▔)/
也欢迎你 关注我(公主号后台回复「送书」即可参与长期看题解学算法送实体书活动)或 加入「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
所有题解已经加入 刷题指南,欢迎 star 哦 ~
统计信息
通过次数 | 提交次数 | AC比率 |
---|---|---|
45572 | 77477 | 58.8% |
提交历史
提交时间 | 提交结果 | 执行时间 | 内存消耗 | 语言 |
---|
相似题目
题目 | 难度 |
---|---|
二叉树展开为链表 | 中等 |