【面试经典150 | 链表】随机链表的复制

文章目录

  • Tag
  • 题目来源
  • 题目解读
  • 解题思路
    • 方法一:哈希表+递归
    • 方法二:哈希表
    • 方法三:迭代+拆分节点
  • 写在最后

Tag

【递归】【迭代】【链表】


题目来源

138. 随机链表的复制

【面试经典150 | 链表】随机链表的复制_第1张图片

题目解读

对一个带有随机指向的链表进行深拷贝操作。


解题思路

本题一共有三种解法,分别是:

  • 哈希表+递归;
  • 哈希表;
  • 迭代+节点拆分。

前两种方法都需要用到哈希表这种基本的数据结构,哈希表中存放的数据都是源节点与深拷贝后的节点这样的键值对,但是一个(方法一)是使用递归进行完成深拷贝任务,而另一个(方法二)使用迭代来完成深拷贝工作,这两种方法属于常规解法了。

第三种解法比较巧妙,省去了哈希表,节省了空间,面试的时候能够清晰的答出该方法一定可以令面试官眼前一亮。

接下来具体看一看这三种解法。

方法一:哈希表+递归

如果是对一个普通的链表进行深拷贝操作,我们直接按照遍历的顺序创建链表即可。但是,本题中的链表有一个随机指针,指向链表中的随机的一个节点,如果还是按照链表顺序拷贝节点,当前拷贝的节点的随机节点可能还没有创建。

于是会有两种不同的思路,一是通过递归的方法创建需要的节点,具体地:

  • 维护一个哈希表 cacheNode 用来存放源节点与深拷贝后的节点;
  • 首先创建头结点的拷贝节点,并存入到哈希表中;
  • 接着递归建立新头结点的下一个节点与随机节点,也就是调用自身;
  • 最后返回 cacheNode[head]

实现代码

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    unordered_map<Node*, Node*> cacheNode;

    Node* copyRandomList(Node* head) {
        if (head == NULL) {
            return NULL;
        }    
        if (!cacheNode.count(head)) {
            Node* newHead = new Node(head->val);
            cacheNode[head] = newHead;
            newHead->next = copyRandomList(head->next);
            newHead->random = copyRandomList(head->random);
        }
        return cacheNode[head];
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 是链表的长度。

空间复杂度: O ( n ) O(n) O(n)


方法二:哈希表

直接使用根据每个节点拷贝出新的节点,存放到哈希表中,然后根据原链表的指向关系更新新节点的指向关系。

实现代码

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    unordered_map<Node*, Node*> cacheNode;

    Node* copyRandomList(Node* head) {
        if (head == NULL) {
            return NULL;
        }    
        
        Node* curr = head;
        while (curr != NULL) {
            cacheNode[curr] = new Node(curr->val);
            curr = curr->next;
        }

        curr = head;
        while (curr != NULL) {
            cacheNode[curr]->next = cacheNode[curr->next];
            cacheNode[curr]->random = cacheNode[curr->random];
            curr = curr->next;
        }
        return cacheNode[head];
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 是链表的长度。

空间复杂度: O ( n ) O(n) O(n)

方法三:迭代+拆分节点

方法三和方法二类似,都是先将原链表拷贝一次,只是本方法是先将拷贝后的链表先连在原链表后,接着就是巧妙的连接与拆分方法,接下来以图示的方式进行分析:

【面试经典150 | 链表】随机链表的复制_第2张图片

原链表为 A->B->C

深拷贝节点并连接next指针

我们先在原链表的节点之后深拷贝一个新的节点并连接,生成一个新的链表 A->A'->B->B'->C->C'。实现的方法与在链表指定节点 node 之后插入一个新的节点类似:

  • 先深拷贝节点 node 得到新的节点 newNode
  • 再将拷贝得到的新节点 newNode 连接到 node->next
  • 最后将 node 连接到 newNode

连接random指针

现在需要解决拷贝后的链表的 random 指针指向问题,node 的下一个节点就是 newNodenewNoderandom 指针指向就是 noderandom 指向,我们就根据 node 来更新就可以。具体实现见代码。

切断两链表之间的联系

解决好 random 指针指向问题,就要切断两个链表之间的联系。具体实现见代码

实现代码

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (head == NULL) {
            return head;
        }

        // 深拷贝节点并连接next指针
        for (Node* node = head; node != NULL; node = node->next->next) {
            Node* newNode = new Node(node->val);
            newNode->next = node->next;
            node->next = newNode;
        }

        // 连接random指针
        for (Node* node = head; node != NULL; node = node->next->next) {
            Node* newNode = node->next;
            newNode->random = (node->random != NULL) ? node->random->next : NULL;
        }

        // 切断两链表之间的联系
        Node* newHead = head->next;
        for (Node* node = head; node != NULL; node = node->next) {
            Node* newNode = node->next;
            node->next = node->next->next;
            newNode->next = (newNode->next != NULL) ? newNode->next->next : NULL;
        }        
        return newHead;
    }
};

时间复杂度: O ( n ) O(n) O(n) n n n 是链表的长度。

空间复杂度: O ( 1 ) O(1) O(1),答案链表不算作额外的空间。


写在最后

如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 。

如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。

最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 哦。

你可能感兴趣的:(面试经典150题,递归,迭代,链表,C++,算法)