剑指Offer35.复杂链表的复制

  • 剑指Offer35.复杂链表的复制
  • 题目:
    在单链表中的结点中增添一个Node* random,指向nullptr或链表中的某个节点;
    复制这个链表,并返回其头结点;
class Node {
    
    
public:
    int val;
    Node* next;
    Node* random;
    Node(int _val) : val(_val), next(nullptr), random(nullptr) {
    
    }
};
  • 思路:
    对于普通单链表,由于只有next指针,因此可以边遍历旧链表结点边构建新链表的结点;
    对于本题,结点还需确定random指针:假设对于原链表的cur,它的random可能指向前面或者后面某个结点或nullptr,要想确定具体在哪,只能从头遍历找到cur->random确定是第几个结点,才能填好cur’->random,因此时间复杂度是O(n ^ 2):遍历每个节点,确定每个结点的random需要O(n),因此在确定结点的random指针上进行优化;

1.哈希表:O(n):遍历了两次旧链表;O(n):额外需要一个哈希表存新旧链表中对应节点的位置
第一次遍历时,用unordered_map存储好旧新链表中对应结点的位置结点S和S‘,这样我们就可以花费O(1)根据S->random找到S’->random;

class Solution {
    
    
public:
    Node* copyRandomList(Node* head) {
    
    
        if (!head) return nullptr;
        
        unordered_map<Node*, Node*> um;//可以通过旧链表节点找到新链表中对应结点的位置;
        auto cur = head;
        while (cur) {
    
    //第一次遍历:只构建出新链表的所有结点;也可以顺便把next先填好,总之random只能第二次遍历时填
            auto p = new Node(cur->val);
            um[cur] = p;
            cur = cur->next;
        }

        cur = head;
        while (cur) {
    
    //第二次遍历:借助um,确定新链表结点的next和random
            um[cur]->next = um[cur->next];
            um[cur]->random = um[cur->random];
            cur = cur->next;
        }

        return cur[head];
    }
};

2.(yxc)先破坏原链表,再修复:O(n):遍历3次,O(1):省去了方法1的哈希表
方法1中的新链表是独立构建的,因此需要哈希表将新就链表结点联系起来;而方法2,在第一次遍历时直接在旧链表中将所有节点复制了一份(为了将新旧节点联系起来,但不用额外空间,就只能暂时破坏旧链表),并在第二次遍历中将新节点的random填好(即旧链表节点S->random不为空时,若s->random指向p,则s的复制节点s‘->next指向p的复制p’),在第三次遍历时将新节点的next填好,并顺便恢复旧链表结点的next*;

class Solution {
    
    
public:
    Node* copyRandomList(Node* head) {
    
    
        if (!head) return nullptr;

        //第一次遍历:原地复制链表所有节点,且仅将next指针连接起来,例如A->B->C变成A->A'->B->B'->C->C'
        //这样就破坏了原链表结点的next*(没破坏random*);
        for (auto p = head; p; ) {
    
    
            auto tmp = p->next;
            p->next = new Node(p->val);
            p->next->next = tmp;
            p = tmp;
        }
		//第二次遍历:填写新复制的结点的random*
        for (auto p = head; p; p = p->next->next) {
    
    
            if (p->random) p->next->random = p->random->next;//若cur->random确实指向链表中的某个节点,旧让cur的复制的random指向cur的random的复制
            //else p->next->random = nullptr;//若A->random为空,则A’->random也为空; 本句可省略:因为在第一次遍历时,复制出来的新节点默认next*和random*都为nullptr
        }

        auto dummy = new Node(-1);//用一个dummy按顺序把新链表节点接起来
        auto cur = dummy;
        //第三次遍历:将链表断开,例如A->A'->B->B'->C->C'变为A'->B'->C',且注意这一步应当将原链表复原(即恢复原链表的next*);
        for (auto p = head; p; p = p->next) {
    
    
            cur->next = p->next;
            cur = cur->next;
            p->next = p->next->next;//把被破坏的原链表结点的next恢复
        }

        return dummy->next;
    }
};
  • 总结:
    y总牛B

猜你喜欢

转载自blog.csdn.net/jiuri1005/article/details/114003804
今日推荐