leetcode430:扁平化多级双向链表
https://leetcode.cn/problems/flatten-a-multilevel-doubly-linked-list
你会得到一个双链表,其中包含的节点有一个下一个指针、一个前一个指针和一个额外的 子指针 。这个子指针可能指向一个单独的双向链表,也包含这些特殊的节点。这些子列表可以有一个或多个自己的子列表,以此类推,以生成如下面的示例所示的 多层数据结构 。
给定链表的头节点 head ,将链表 扁平化 ,以便所有节点都出现在单层双链表中。让 curr
是一个带有子列表的节点。子列表中的节点应该出现在扁平化列表中的 curr
之后 和 curr.next
之前 。
返回 扁平列表的 head
。列表中的节点必须将其 所有 子指针设置为 null
。
简单来说就是把多层的链表通过插入合成一条链表
自用,感觉dfs版比较绕,得多复习几次,很容易出错
方法1:递归
出现这种一级一级嵌套很容易想到使用递归,但是递归的代码没有那么好写
class Solution {
public:Node* flatten(Node* head) {Node* curr = head;while (curr) {if (curr->child) {Node* next = curr->next;Node* childHead = flatten(curr->child);// 插入 child 链表curr->next = childHead;childHead->prev = curr;curr->child = nullptr;// 找到 child 链表的尾部Node* tail = childHead;while (tail->next) {tail = tail->next;}// 把尾部和原来的 next 连接if (next) {tail->next = next;next->prev = tail;}}curr = curr->next;}return head;}
};
首先要明确要干什么
在每一层递归中要完成断开原来的连接,把断开的位置接上
在当前层循环中完成两件事,其一是检测当前层的每一个节点的child指针是否为空
其二是完成相关的连接
在if语句内部也完成两件事,分别是平坦化和连接
但是我们会注意到
由于递归使得主链的curr指针总是把之前走过的点又走一遍
比如:这里虽然你已经平坦化,并把下面的部分都插入到了3和4之间,但是由于curr指针仍然在3,想要走到4仍然要沿着绿线走一遍,增加了时间复杂度
所以我们就可以考虑记录每一次的链表末尾,
如果有一个函数能同时完成平坦化并且返回链表末尾的指针就好了,
但是原函数返回的是链表的头指针,因此我们得自己写一个函数辅助
方法2:递归+dfs函数优化
这里参考力扣官方题解
class Solution {
public:Node* flatten(Node* head) {function<Node*(Node*)> dfs = [&](Node* node) {Node* cur = node;// 记录链表的最后一个节点Node* last = nullptr;while (cur) {Node* next = cur->next;// 如果有子节点,那么首先处理子节点if (cur->child) {Node* child_last = dfs(cur->child);next = cur->next;// 将 node 与 child 相连cur->next = cur->child;cur->child->prev = cur;// 如果 next 不为空,就将 last 与 next 相连if (next) {child_last->next = next;next->prev = child_last;}// 将 child 置为空cur->child = nullptr;last = child_last;}else {last = cur;}cur = next;}return last;};dfs(head);return head;}
};作者:力扣官方题解
链接:https://leetcode.cn/problems/flatten-a-multilevel-doubly-linked-list/solutions/1013884/bian-ping-hua-duo-ji-shuang-xiang-lian-b-383h/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
其中函数的主要部分就是这个dfs函数
补充:关于lambda表达式
其实只是把「递归函数」定义成 lambda 表达式,再存到 std::function
里,以便 lambda 内部能调用自己(实现递归)。
拆开看就四件事:
-
std::function<Node*(Node*)>
一个函数对象类型,签名是「接收Node*
,返回Node*
」。 -
dfs
变量名,后面用它来递归。 -
= [&]
捕获列表:-
[&]
表示「所有外部变量按引用捕获」,这样 lambda 里才能看见并调用 自己(dfs)。
-
-
(Node* node) { ... }
真正的函数体,和普通函数写法一模一样。
假如孩子指针全为空,代码执行
class Solution {
public:Node* flatten(Node* head) {function<Node*(Node*)> dfs = [&](Node* node) {Node* cur = node;// 记录链表的最后一个节点Node* last = nullptr;while (cur) {Node* next = cur->next;last = cur;cur = next;}return last;};dfs(head);return head;}
};这样相当于就遍历了一次链表,返回了最后一个位置,也是没有问题的