【数据结构与算法】剑指 Offer 35. 复杂链表的复制

简介: 1.将链表节点和next复制(也就是上面普通链表的复制)2.遍历旧的链表查看每个节点N对应的random指向的节点M,然后从旧链表的首部开始遍历, 看什么时候走几步能到M, 得到步数3.根据步数在新链表里查找M* 节点, 并让节点 N* 指向 M*

剑指 Offer 35. 复杂链表的复制

👉题目:请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next指针指向下一个节点,

还有一个 random 指针指向链表中的任意节点或者 null。👈


🌟普通链表

Node(int value) {
        val = value;
        next = NULL;
}




🎄题目中定义的复杂链表

Node(int _val) {
        int val = _val;
        Node *next = NULL;
        Node *random = NULL;
}


🌈难点:


random的复制, 因为新的链表对应的random不是相邻的节点, 是随机的节点, 因此将这种关系复制给新链表就不是一件很容易的事.


💡法一:暴力(保底法)

1.将链表节点和next复制(也就是上面普通链表的复制)

2.遍历旧的链表查看每个节点N对应的random指向的节点M,然后从旧链表的首部开始遍历, 看什么时候走几步能到M, 得到步数

3.根据步数在新链表里查找M* 节点, 并让节点 N* 指向 M*


👉注意: 找M*节点时, 不是按照值找的(a == b), 而是按照地址来找的(*a == *b), 因为在遇到真正的M之前可能会遇到与它值相同的节点, 所以按地址值查找


✨复杂度的分析 : 对于一个含有 N 个节点的链表, 由于定位每一个节点的 random 都需要从链表头节点开始经过 O(n)的时间复杂度才能找到, 因此这种方法的时间复杂度是 O(n^2)

Node* copyRandomList(Node* head) {
    if(head == nullptr) return nullptr;
    Node* cur = head;   // cur相当于头节点的副本
    Node* temp = nullptr;
    Node* res;
    res = temp;
    // 1. 复制各节点
    while(cur != nullptr) {
        Node *ptmp = new Node(cur->val);
        ptmp->next = nullptr;
        temp->next = ptmp;
        temp = temp->next;
        cur = cur->next;
    }
  res = res->next;
  Node *ans = res;
    // 2. 遍历旧链表得到步数并进行复制
    cur = head;
    while(cur != nullptr) {
    temp = cur.random;
    Node *pp = head;
    int count = 0;
    while(pp != nullptr && pp != temp) {
      pp = pp->next;
      count++;
    }
    pp = res;
    while(pp != nullptr && --count != 0) {
      pp = pp->next;
    }
    res->random = pp;
    cur = cur->next;
    res = res->next;
  }
    return res;      // 返回新链表头节点
}


💡法二 : 哈希法(空间换时间)

优化 : 找链表的节点, 找这个行为可以用什么来优化

🎉没错就是哈希🎉

哈希可以进行很快的查找, 时间复杂度为 O(1)

体现了空间换时间



将旧链表的节点和新链表的节点插入到哈希表中得到对应关系 <N, N*>, 则通过N->random得到的节点M, 通过哈希就可以得到 M* , 岂不美哉.


✨复杂度的分析 : 相较于法一, 我们用空间换时间, 需要一个大小为 O(n)的哈希表, 等同于用 O(n)的空间将时间复杂度从 O(n^2)降低到 O(n)

Node* copyRandomList(Node* head) {
    if(head == nullptr) return nullptr;
    Node* cur = head;
    unordered_map<Node*, Node*> map;
    // 1. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
    while(cur != nullptr) {
        map[cur] = new Node(cur->val);
        cur = cur->next;
    }
    cur = head;
    // 2. 构建新链表的 next 和 random 指向
    while(cur != nullptr) {
        map[cur]->next = map[cur->next];
        map[cur]->random = map[cur->random];
        cur = cur->next;
    }
    // 3. 返回新链表的头节点
    return map[head];
}


💡法三 : 叠加复制 + 截取

🌈有辅助空间的写法, 那么就有 没有辅助空间的写法

一种巧妙但又不容易发现的方法, 也是需要我们去积累的方法叠加复制 + 截取

具体步骤是:

1.在每一个节点的后面加一个节点

2.复制新的指针的时候 next 正常前后连接

3.新节点的 random 根据前一个节点的random 得到并向后移动一个单位


具体如图所示:



✨复杂度的分析 : 相对前两种方法, 我们用空间复杂度和时间复杂度都是最优的, 时间上我们需要三次遍历, 第一次建立新的节点, 第二次复制random, 最后再删除奇数位上的节点即可, 空间上, 只需要一个新链表的节点的空间时间复杂度也为 O(n)


Node* copyRandomList(Node* head) {
    if(head == nullptr) return nullptr;
    Node* cur = head;   // cur相当于头节点的副本
    // 1. 复制各节点,并构建拼接链表
    while(cur != nullptr) {
        Node* tmp = new Node(cur->val);
        tmp->next = cur->next;
        cur->next = tmp;
        cur = tmp->next;
    }
    // 2. 构建各新节点的 random 指向
    cur = head;
    while(cur != nullptr) {
        if(cur->random != nullptr)
            cur->next->random = cur->random->next;
        cur = cur->next->next;
    }
    // 3. 拆分两链表
    cur = head->next;
    Node* pre = head, *res = head->next;
    while(cur->next != nullptr) {
        pre->next = pre->next->next;
        cur->next = cur->next->next;
        pre = pre->next;
        cur = cur->next;
    }
    pre->next = nullptr; // 单独处理原链表尾节点
    return res;      // 返回新链表头节点
}



🏁总结 : 将复杂问题, 分为多个小问题, 一个一个进行解决, 首先是节点和next的复制, 比较容易, 但是刚开始会发现random的复制不是很容易, 这个时候你就应该想, 那种数据结构可以将查找的速率优化呢, 是二分和哈希, 然后你进行分析, 发现这里不具有二段性, 所以你使用哈希进行优化, 如此你得到了O(n)的时间复杂度, 但是无巧不成题, 你还是会再进行深究, 那么你会发现更深的结构, 那就是链表自身的结构, 链表的连接方法就产生了这种巧妙的方法拼接+拆分, 于是乎你再进行整改, 得到几乎很快的方法, 然后你将这道题完全解决, 🎉从此在算法界里又离蒟蒻远了那么一点点, 🌈离算法岗又近了那么一个点.


相关文章
|
12天前
|
存储
数据结构第二课 -----线性表之单向链表
数据结构第二课 -----线性表之单向链表
|
2天前
|
Java C语言
剑指offer(牛客)——合并两个排序的链表
剑指offer(牛客)——合并两个排序的链表
7 1
|
2天前
|
存储 Java C语言
剑指offer(牛客)——从尾到头打印链表
剑指offer(牛客)——从尾到头打印链表
8 1
|
2天前
|
存储 算法 Java
数据结构与算法 数组和链表
数据结构与算法 数组和链表
8 0
|
2天前
|
存储 Java
深入浅出数据结构之链表
深入浅出数据结构之链表
|
3天前
|
C++
数据结构(双链表
数据结构(双链表
7 1
|
6天前
|
存储 缓存
[数据结构]~双向+循环链表从(0~1)
[数据结构]~双向+循环链表从(0~1)
|
12天前
|
存储
数据结构第三课 -----线性表之双向链表
数据结构第三课 -----线性表之双向链表
|
12天前
|
存储 Java
数据结构奇妙旅程之顺序表和链表
数据结构奇妙旅程之顺序表和链表
|
17天前
|
存储 C语言
数据结构基础:双链表结构、实现
数据结构基础:双链表结构、实现