LeetCode Notes_#430_扁平化多级双向链表
Contents
题目
多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。
给你位于列表第一级的头节点,请你扁平化列表,使所有结点出现在单级双链表中。
示例 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]
解释:
输入的多级列表如下图所示:
扁平化后的链表如下图:
提示:
- 节点数目不超过 1000
- 1 <= Node.val <= 10^5
思路
我们将列表顺时针转 90 °,那么就会看到一颗二叉树,则扁平化的操作也就是对二叉树进行先序遍历(深度优先搜索)。更准确的说,除了按照先序遍历的顺序访问,还需要注意的还需要按对节点进行双向连接。
在图中,我们将child
看作二叉树的left
指针,将next
指针看作二叉树中的right
指针。
递归函数Node flattenDFS(Node prev, Node cur)
两个参数就是扁平化后的双向链表的相邻两个节点。
返回值是tail
节点,即每一个节点的最小的左孩子节点。
终止条件
cur == null
,说明它的前一个节点prev
就已经是最左下角的那个节点(即tail
),直接返回prev
。
递推过程
prev
与next
之间建立双向连接- 暂存
cur.next
节点到变量tmpNext
中,因为cur.next
在递归调用过程中会发生改变 - 递归调用
Node tail = flattenDFS(cur, cur.child);
,将链表看成是二叉树的话,相当于递归访问左子树的过程。- 将每对具有父子关系的节点建立双向连接。
- 返回
tail
节点,即当前已经做完扁平化处理的链表的最后一个节点,这个节点要连接到tmpNext
。
- 删除
cur.child
指针,原因是cur
/cur_child
之间已经建立了双向连接,child
指针就不需要了。 - 递归调用并返回值
return flattenDFS(tail, tmpNext);
,将链表看成是二叉树的话,相当于递归访问右子树的过程。- 左子树的最后一个节点是刚才记下的
tail
,将其连接到右子树的第一个节点tmpNext
。 - 返回值是
tmpNext
的最左下角节点。
- 左子树的最后一个节点是刚才记下的
解答
class Solution {
public Node flatten(Node head) {
if(head == null) return head;
//因为head没有prev指针,所以添加一个dummyHead,dummyHead.next = head,那么可以直接调用递归函数处理
Node dummyHead = new Node(0, null, head, null);
flattenDFS(dummyHead, head);
//断开head.prev指针
dummyHead.next.prev = null;
return dummyHead.next;
}
//这里的prev/cur指的是在扁平列表中,需要建立双向连接的两个相邻节点,可能是父子关系,也可能本来就是相邻关系
//分为cur有孩子,没孩子两种情况
private Node flattenDFS(Node prev, Node cur){
if(cur == null) return prev;
cur.prev = prev;
prev.next = cur;
//cur.next可能会改变,所以要提前保存
Node tmpNext = cur.next;
//将child看作左子树,在cur和cur.child之间建立双向连接
//tail的意思是,如果cur.child还有左孩子,那么会不断递归,直到最小的左孩子,记作tail
//有孩子的情况下,tail是最小的孩子,没孩子的情况下,tail就是cur本身
Node tail = flattenDFS(cur, cur.child);
//已经建立了双向连接后,就不需要child指针了,将它断开
cur.child = null;
//有孩子的情况下,将最小的左孩子遍历完之后,之后连接第一个右孩子
return flattenDFS(tail, tmpNext);
}
}
复杂度分析
时间复杂度:O(n),n是节点个数。
空间复杂度:O(n),递归调用层数是n,则占用栈空间是O(n)。