【算法】430.扁平化多级双向链表--通俗讲解

通俗易懂讲解"扁平化多级双向链表"算法题目

一、题目是啥?一句话说清

给定一个多级双向链表,其中节点可能包含指向子链表的指针,要求将所有层级的节点扁平化到同一层级,保持原有顺序。

示例:

  • 输入:1 ↔ 2 ↔ 3 ↔ 4

    5 ↔ 6 ↔ 7

    8 ↔ 9
  • 输出:1 ↔ 2 ↔ 5 ↔ 6 ↔ 8 ↔ 9 ↔ 7 ↔ 3 ↔ 4

二、解题核心

使用深度优先搜索(DFS),当遇到有子节点的节点时,先处理子链表,将子链表插入到当前节点和下一个节点之间,然后继续处理。

这就像整理一个多层文件夹结构,把子文件夹里的文件都拿出来,按顺序放在主文件夹中。

三、关键在哪里?(3个核心点)

想理解并解决这道题,必须抓住以下三个关键点:

1. 深度优先遍历

  • 是什么:当遇到有子节点的节点时,先深入处理子链表,再继续处理主链表。
  • 为什么重要:这确保了子链表中的节点能够正确地插入到主链表中,保持深度优先的顺序。

2. 指针的正确连接

  • 是什么:在插入子链表时,需要正确调整当前节点、子链表头、子链表尾和下一个节点之间的前后指针。
  • 为什么重要:如果指针连接错误,会导致链表断裂或形成环,破坏链表结构。

3. 子指针的处理

  • 是什么:处理完子链表后,需要将节点的子指针设置为null。
  • 为什么重要:这是题目要求的一部分,确保扁平化后的链表所有子指针都为null。

四、看图理解流程(通俗理解版本)

假设多级链表如下:

复制代码
1 ↔ 2 ↔ 3 ↔ 4
     │
     5 ↔ 6 ↔ 7
          │
          8 ↔ 9
  1. 遍历到节点2

    • 发现节点2有子节点5
    • 先处理子链表:5 ↔ 6 ↔ 7(其中6有子节点8)
  2. 处理子链表5 ↔ 6 ↔ 7

    • 遍历到节点6,发现它有子节点8
    • 先处理子子链表:8 ↔ 9
    • 将8 ↔ 9插入到6和7之间:6 ↔ 8 ↔ 9 ↔ 7
    • 子链表变为:5 ↔ 6 ↔ 8 ↔ 9 ↔ 7
  3. 将子链表插入主链表

    • 将5 ↔ 6 ↔ 8 ↔ 9 ↔ 7插入到2和3之间
    • 主链表变为:1 ↔ 2 ↔ 5 ↔ 6 ↔ 8 ↔ 9 ↔ 7 ↔ 3 ↔ 4
  4. 设置子指针为null

    • 将节点2和节点6的子指针设置为null

五、C++ 代码实现(附详细注释)

cpp 复制代码
#include <iostream>
using namespace std;

// 多级双向链表节点定义
class Node {
public:
    int val;
    Node* prev;
    Node* next;
    Node* child;

    Node(int _val) : val(_val), prev(nullptr), next(nullptr), child(nullptr) {}
};

class Solution {
public:
    Node* flatten(Node* head) {
        if (head == nullptr) return nullptr;
        
        Node* current = head;
        while (current != nullptr) {
            // 如果当前节点有子节点
            if (current->child != nullptr) {
                // 保存当前节点的下一个节点
                Node* next = current->next;
                
                // 递归扁平化子链表
                Node* childHead = flatten(current->child);
                current->child = nullptr; // 将子指针设为null
                
                // 将当前节点与子链表头连接
                current->next = childHead;
                childHead->prev = current;
                
                // 找到子链表的尾节点
                Node* childTail = childHead;
                while (childTail->next != nullptr) {
                    childTail = childTail->next;
                }
                
                // 将子链表尾与下一个节点连接
                if (next != nullptr) {
                    childTail->next = next;
                    next->prev = childTail;
                }
                
                // 移动到子链表处理后的下一个节点
                current = next;
            } else {
                // 没有子节点,继续遍历
                current = current->next;
            }
        }
        return head;
    }
};

// 辅助函数:打印双向链表
void printList(Node* head) {
    Node* current = head;
    while (current != nullptr) {
        cout << current->val << " ";
        current = current->next;
    }
    cout << endl;
}

// 测试代码
int main() {
    // 构建示例多级链表
    Node* head = new Node(1);
    head->next = new Node(2);
    head->next->prev = head;
    head->next->next = new Node(3);
    head->next->next->prev = head->next;
    head->next->next->next = new Node(4);
    head->next->next->next->prev = head->next->next;
    
    // 创建子链表 5->6->7
    head->next->child = new Node(5);
    head->next->child->next = new Node(6);
    head->next->child->next->prev = head->next->child;
    head->next->child->next->next = new Node(7);
    head->next->child->next->next->prev = head->next->child->next;
    
    // 创建子子链表 8->9
    head->next->child->next->child = new Node(8);
    head->next->child->next->child->next = new Node(9);
    head->next->child->next->child->next->prev = head->next->child->next->child;

    Solution solution;
    Node* result = solution.flatten(head);
    
    printList(result); // 输出:1 2 5 6 8 9 7 3 4
    
    // 释放内存(简单示例)
    // 注意:由于链表已经扁平化,需要小心释放以避免重复释放
    // 这里为了简单,不完整释放
    return 0;
}

六、时间空间复杂度

  • 时间复杂度:O(n),其中n是所有节点的总数(包括所有层级的节点)。每个节点被访问一次。
  • 空间复杂度:O(d),其中d是链表的最大深度。这是由于递归调用栈的空间。最坏情况下,如果链表是一条链状的多级结构,d可能等于n。

七、注意事项

  • 递归深度:对于深度很大的多级链表,递归可能导致栈溢出。可以考虑使用迭代方法(如栈)来避免递归。
  • 指针连接顺序:在插入子链表时,要确保正确连接所有指针,包括prev和next指针。
  • 子指针处理:处理完子链表后,一定要将子指针设置为null,满足题目要求。
  • 边界条件:处理空链表、只有一层链表、子链表为空等情况。
  • 内存管理:在C++中,如果需要释放内存,要注意链表已经扁平化,避免重复释放或遗漏释放。
相关推荐
yongui478343 小时前
INTLAB区间工具箱在区间分析算法中的应用与实现
数据结构·算法
今天也好累6 小时前
贪心算法之会议安排问题
c++·笔记·学习·算法·贪心算法
KWTXX9 小时前
【国二】C语言-部分典型真题
java·c语言·算法
lucialeia10 小时前
leetcode (4)
算法·leetcode
永远有多远.10 小时前
【CCF-CSP】第39次CSP认证前三题
算法
墨染点香11 小时前
LeetCode 刷题【90. 子集 II】
算法·leetcode·职场和发展
Code_LT13 小时前
【算法】多榜单排序->综合排序问题
人工智能·算法
仟濹13 小时前
【力扣LeetCode】 1413_逐步求和得到正数的最小值
算法·leetcode·职场和发展
9毫米的幻想13 小时前
【Linux系统】—— 进程切换&&进程优先级&&进程调度
linux·运维·服务器·c++·学习·算法