[链表]–扁平化多级双向链表
题目链接
题目
多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。
给你位于列表第一级的头节点,请你扁平化列表,使所有结点出现在单级双链表中。
示例
输入链表:
输出:
输入:head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
也就是这样的: 1---2---3---4---5---6--NULL
|
7---8---9---10--NULL
|
11--12--NULL
输出:[1,2,3,7,8,11,12,9,10,4,5,6]
解析
每一层有 child 的节点需要先连接 child,遍历完了再继续遍历连接上一层,这很递归!
0. 递归终止:咱没有 child 就终止了;
- 假设这有 head 的是第一级,chld 是第二级,依次向下。首先肯定是遍历链表了;
- 如果说没有 child,那就继续向同一级后面走;有的话就需要向下一级走了;
- 上一步说到这个存在 child 的当前节点 cur 需要向下走了。接着来:
(1)首先为了防止走丢,先把 cur.next 保存起来(curNext=cur.next);接下来就大胆向下走
(2)万一这 child 还有 child?这还得继续向下走啊!哟典型递归。递什么?有孩子节点就向下递;归呢?自底向上每次遍历完了整个子链表就归。需要注意的是 child 遍历过了(作为了“父节点”的 next )就置为 null,不然这个节点还存在就不是双向链表了;
(3)说回来:归的时候就需要连接了,将链表的尾结点和上一级的 curNext 连接起来,然后依次向上归,直到遍历完第一级的链表。还有个注意的:假如说原本的的 curNext是 null,这一层就不需要连接了,直接继续向上。- 递归不好理解啊,所以头向左一偏,瞅一眼这示例图:有点二叉树的味了!那就试试!
代码实现
public class Solution430 {
/**
* Definition for a node
*/
class Node {
int val;
Node prev;
Node next;
Node child;
}
/**
* 将 head 所在一级视为第一级, 子链表所在一级看作下一级, 依次向下; 将有 child 的结点看作 "父结点"————递归
* 1.没有 child 的结点不需要改变, 向后遍历即可, 有 child 的 "父结点" 去遍历 child 这一子链表
* 2.递归进行上一操作, 由上而下, 也就是有 child 就先去遍历子链表, 直到最后一个子链表被遍历
* 3.递归到最后一层子链表, 将末尾结点的 next 指向上一级 "父结点" 的下一个连接起来,
* !!注意需要修改 "父结点" 的 child = null(否则不是一个双向链表, 因为还存在 child)
* 4.由下而上, 将每一级的子链表的最后一个连接到响应位置, 直到回到第一级链表遍历完
*/
public Node flatten(Node head) {
if (head == null) return null;
Node cur = head;
while (cur != null) {
// 没有 child 结点就保持原有连接
if (cur.child == null) {
cur = cur.next;
}else {
// 有 child 结点
// 保存有 child 结点这一级的 next -> 连接子链表的最后一个结点
Node curNext = cur.next;
// 递归遍历所有的子链表, 并把子链表连接在子链表 "父结点"(上一级)后面
Node child = flatten(cur.child);
cur.next = child;
child.prev = cur;
cur.child = null;
// 向后遍历,直到遍历完当前这一级
while (cur.next != null) {
cur = cur.next;
}
// 将子链表最后一个结点连接在 curNext 之前
if (curNext != null) {
cur.next = curNext;
curNext.prev = cur;
}
}
}
return head;
}
}
说到整体图看起来像二叉树,那就可以把 child 看作左子树,next 看作右子树,然后这结果不是前序遍历嘛
用栈!
- 头节点不为 null 入栈,为了更好连接:定义一个 dummyHead 是傀儡头结点,定义一个 prev 作为弹出节点的前驱节点;
- 弹出栈顶节点 temp 进行连接,prev.next = temp;temp.prev = prev;判断弹出节点的左右子树(child 和 next)不为空就压栈(先右后左压栈)。右子树(child)压栈需要置为 null;
- prev 再次作为弹出节点的前驱节点用于连接;继续遍历直至栈为空;
- 最后头节点的前驱置为 null。
代码实现
public class Solution430 {
/**
* Definition for a node
*/
class Node {
int val;
Node prev;
Node next;
Node child;
}
/**
* 将多级双向链表看作是二叉树, head 就是根节点, 前序遍历即可————迭代
* 1.定义 prev 结点用于记录当前弹出结点 cur 的前驱结点
* 2.判断 cur 的 next 和 child 是否为空, 不为空入栈
* 3.child 结点入栈后需要将其"父结点"的 child 修改为 null
*/
public Node flatten(Node head) {
if (head == null) return null;
// 傀儡头结点
Node dummyHead = new Node();
// 记录遍历结点的前驱结点 -> 为了方便进行结点之间的连接
Node prev = dummyHead;
Stack<Node> stack = new Stack<>();
stack.push(head);
while (!stack.isEmpty()) {
// 弹出栈顶结点, 同时连接它和它的前驱结点
Node temp = stack.pop();
prev.next = temp;
temp.prev = prev;
// next 不为空入栈————相当于右子树
if (temp.next != null) {
stack.push(temp.next);
}
// child 不为空入栈————相当于左子树
// 注意需要将 child 置为 null, 否则不是一个双向链表, 还存在 child
if (temp.child != null) {
stack.push(temp.child);
temp.child = null;
}
// prev 跟着 temp 移动
prev = temp;
}
// 头结点的前驱是 null
dummyHead.next.prev = null;
return dummyHead.next;
}
}
-----------------------------------------------------------------------------有始有终分割线----------------------------------------------------------------------------------