多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。
给定位于列表第一级的头节点,请扁平化列表,即将这样的多级双向链表展平成普通的双向链表,使所有结点出现在单级双链表中。
扁平化后的链表如下图:
解题思路:
在展平链表时,我们希望先处理一个节点的子链表,再处理它的下一个节点。这种先深入再回溯的过程正是深度优先遍历(DFS)的特点。DFS可以通过递归实现,但考虑到链表节点的修改操作(更新prev和next指针),我们选择栈来模拟递归。
使用栈存储待处理的节点,初始时将链表头节点压入栈中。通过循环不断从栈中弹出节点进行处理,优先将next节点压入栈,然后将child节点压入栈。在处理每个节点时,更新其prev和next指针,以展平链表。
因为栈是后进先出(LIFO),先压入的元素后弹出。为了确保在遍历当前节点的子节点之后再继续遍历它的下一个节点,我们需要先将next节点压入栈,然后再将child节点压入栈。这样,栈顶先是child节点,待子链表处理完毕后,再处理next节点,确保了正确的遍历顺序。
示例:
假设链表结构如下:
1 - 2 - 3
|
4 - 5
|
6
1的next是2。
2的child是4,next是3。
4的next是5。
5的child是6。
遍历步骤:
初始栈:[1]
弹出1,压入2。栈:[2]
弹出2,压入3,再压入4。栈:[3, 4]
弹出4,压入5。栈:[3, 5]
弹出5,压入6。栈:[3, 6]
弹出6。栈:[3]
弹出3。栈:[]
遍历顺序:1 -> 2 -> 4 -> 5 -> 6 -> 3,展平后的链表顺序是正确的。
/*
// Definition for a Node.
class Node {
public int val;
public Node prev;
public Node next;
public Node child;
};
*/
class Solution {
public Node flatten(Node head) {
if (head == null) {
return head;
}
// 使用一个伪头部(dummy head),便于处理链表的头部节点
Node dummyHead = new Node(0);
dummyHead.next = head;
head.prev = dummyHead;
// 使用一个栈来存储节点,便于深度优先遍历
Deque<Node> stack = new ArrayDeque<>();
stack.push(head);
Node prev = dummyHead;
while (!stack.isEmpty()) {
Node curr = stack.pop();
// 处理当前节点
prev.next = curr;
curr.prev = prev;
// 如果当前节点有下一个节点,先将下一个节点压入栈中
if (curr.next != null) {
stack.push(curr.next);
}
// 如果当前节点有子节点,将子节点压入栈中
if (curr.child != null) {
stack.push(curr.child);
// 处理完子节点后,将当前节点的子节点指针置为空
curr.child = null;
}
// 更新前驱节点
prev = curr;
}
// 断开伪头部与真实头部的连接
dummyHead.next.prev = null;
return dummyHead.next;
}
}