leetcode:138. 复制带随机指针的链表

题目来源

  • leetcode:138. 复制带随机指针的链表

题目描述

leetcode:138. 复制带随机指针的链表_第1张图片

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) {
        
    }
};

题目解析

迭代:

初始化一个map,第一次遍历原始链表,使用这些链表的值拷贝,并且map[旧节点] = 新节点

第二次遍历链表,给新节点之间连上线

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

        Node *origin = head;
        std::unordered_map<Node *, Node *> map;
        while (origin){
            Node *clone = new Node(origin->val);
            map[origin] = clone;
            origin = origin->next;
        }

        origin = head;
        while (origin){    // 当前节点不为空
            if(origin->next){  // 当前节点的下一个节点不为空
                map[origin]->next = map[origin->next];
            }

            if(origin->random){
                map[origin]->random = map[origin->random];
            }
            origin = origin->next;
        }

        return map[head];
    }
};

回溯

回溯算法的第一想法是将链表想象成一张图。链表中每个节点都有2个指针(图中的边)。因为随机指针给图结构添加了随机性,所以我们可能会访问相同的节点多次,这样就形成了环
leetcode:138. 复制带随机指针的链表_第2张图片
上图中,我们可以看到随机指针指向了前一个节点,因此形成了环。我们需要考虑这种环的实现。

此方法中,我们只需要遍历整个图并且拷贝它。拷贝的意思是每当遇到一个新的没有访问过的节点,你都要创造一个新的节点。遍历按照深度优先进行。我们需要在回溯的过程中记录已经访问过的节点,否则因为随机指针的存在进入死循环。

算法:

  • 头指针开始遍历整张图
    我们将链表看做一张图。下图对应的是上面的有向链表的例子,head是图的出发节点。
    leetcode:138. 复制带随机指针的链表_第3张图片

  • 当我们遍历到某个点时,如果我们已经有了当前节点的一个拷贝,我们不需要重复进行拷贝。
    如果我们还没拷贝过当前节点,我们创造一个新的节点,并把该节点放到已访问字典中

  • 我们针对两种情况进行回溯调用:一个顺着 random 指针调用,另一个沿着 next 指针调用。步骤 1 中将 random 和 next 指针分别红红色和蓝色标注。然后我们分别对两个指针进行函数递归调用:

class Solution {
    std::unordered_map<Node *, Node *> map;
public:
    Node* copyRandomList(Node* head) {
        if (head == nullptr){
            return nullptr;
        }

        if(map.count(head)){
            return map[head];
        }
        
        Node *node = new Node(head->val);
        node->next = nullptr;
        node->random = nullptr;
        
        map[head] = node;
        
        node->next = copyRandomList(head->next);
        node->random = copyRandomList(head->random);
        
        return node;
    }
};

时间复杂度:O(N) ,其中 NN 是链表中节点的数目。

空间复杂度:O(N)。如果我们仔细分析,我们需要维护一个回溯的栈,同时也需要记录已经被深拷贝过的节点,也就是维护一个已访问字典。渐进时间复杂度为 O(N)。

O(N)空间的迭代

  • 维护一个旧节点和新节点的map
class Solution {
    std::unordered_map<Node *, Node *> map;
    Node *getCloneNode(Node *node){
        if(node == nullptr){
            return nullptr;
        }
        
        if(map.count(node)){
            return map[node];
        }else{
            Node *newNode = new Node(node->val);
            map.insert(node, newNode);
            return map[node];
        }
    }
public:
    Node* copyRandomList(Node* head) {
        if (head == nullptr){
            return nullptr;
        }
        
        Node *oldNode = head;
        Node *newNode = new Node(head->val);
        map.insert(head, newNode);
        
        while (oldNode != nullptr){
            newNode->next = getCloneNode(oldNode->next);
            newNode->random = getCloneNode(oldNode->random);
            newNode = newNode->next ;
            oldNode = oldNode->next;
        }
         
        return map[head];
    }
};

时间复杂度:O(N)。因为我们需要将原链表逐一遍历。
空间复杂度:O(N)。 我们需要维护一个字典,保存旧的节点和新的节点的对应。因此总共需要 N 个节点,需要 O(N)的空间复杂度。

O(1)空间的迭代

  • 从左到右,对每个节点都复制一个副本节点copy,将拷贝节点放在原来节点的旁边,创造出一个旧节点和新节点交错的链表(比如原来1-2->null,变成1->1’->2->2’->null)

cloned_node.next = original.next
original.next = cloned_node
leetcode:138. 复制带随机指针的链表_第4张图片

  • 迭代这个新旧交替的链表,并用旧节点的random指针去更新对应新节点的random指针。比方说, B 的 random 指针指向 A ,意味着 B’ 的 random 指针指向 A’
    leetcode:138. 复制带随机指针的链表_第5张图片
  • 现在 random 指针已经被赋值给正确的节点, 此时所有的节点和副本节点都串在了一(1->1’->2->2’->null),将其分离出来即可(1->2->null;1’->2’->null)。
    leetcode:138. 复制带随机指针的链表_第6张图片
class Solution {

public:
    Node* copyRandomList(Node* head) {
        if (head == nullptr){
            return nullptr;
        }
        
        Node *origial = head;
        while (origial != nullptr){
            Node *node = new Node(origial->val);
            node->next = origial->next;
            origial->next = node;
            origial = origial->next->next;
        }

        // 跳出循环之后:origial = null
        origial = head;
        while (origial != nullptr){
            if (origial->random != nullptr){
                origial->next->random = origial->random->next;
            }
            origial = origial->next->next;
        }


       //拆分
        origial = head;
        Node *clone = head->next;
        Node *cur_clone = clone;
        while (origial!= nullptr && cur_clone != nullptr){
            // 原始链表
            origial->next = origial->next == clone ? clone : origial->next->next;
            origial = origial->next;

            cur_clone->next = cur_clone->next == clone ? clone : cur_clone->next->next;
            cur_clone = cur_clone->next;
        }

        return clone;
    }
};

类似题目

题目 思路
leetcode:133. 克隆图 Clone Graph
leetcode:138. 复制带随机指针的链表 Copy List with Random Pointer

你可能感兴趣的:(算法与数据结构,链表,leetcode,数据结构)