[剑指-Offer] 35. 复杂链表的复制(HashMap、递归、代码优化)

1. 题目来源

链接:复杂链表的复制
来源:LeetCode——《剑指-Offer》专项

2. 题目说明

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null

示例1:
在这里插入图片描述

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例2:
在这里插入图片描述

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例3:
在这里插入图片描述

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示:

  • -10000 <= Node.val <= 10000
  • Node.random 为空(null)或指向链表中的节点。
  • 节点数目不超过 1000 。

3. 题目解析

方法一:辅助HashMap+迭代解法

这道题目的难点在于如何处理随机指针的问题,由于每一个节点都有一个随机指针,这个指针可以为空,也可以指向链表的任意一个节点,如果在每生成一个新节点给其随机指针赋值时,都要去遍历原链表的话,OJ 上很有可能会 TLE,所以可以考虑用 HashMap 来缩短查找时间,第一遍遍历生成所有新节点时同时建立一个原节点和新节点的 HashMap,第二遍给随机指针赋值时,查找时间是常数级。

参见代码如下:

// 执行用时 :8 ms, 在所有 C++ 提交中击败了94.82%的用户
// 内存消耗 :13.6 MB, 在所有 C++ 提交中击败了100.00%的用户

/*
// 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) return nullptr;
        Node *res = new Node(head->val);
        Node *node = res, *cur = head->next;
        unordered_map<Node*, Node*> m;
        m[head] = res;
        while (cur) {
            Node *t = new Node(cur->val, nullptr, nullptr);
            node->next = t;
            m[cur] = t;
            node = node->next;
            cur = cur->next;
        }
        node = res; cur = head;
        while (cur) {
            node->random = m[cur->random];
            node = node->next;
            cur = cur->next;
        }
        return res;
    }
};

方法二:辅助HashMap+递归解法

我们可以使用递归的解法,写起来相当的简洁,还是需要一个 HashMap 来建立原链表结点和拷贝链表结点之间的映射。在递归函数中,首先判空,若为空,则返回空指针。然后就是去 HashMap 中查找是否已经在拷贝链表中存在了该结点,是的话直接返回。否则新建一个拷贝结点 res,然后建立原结点和该拷贝结点之间的映射,然后就是要给拷贝结点的 nextrandom 指针赋值了,直接分别调用递归函数即可。

参见代码如下:

class Solution {
public:
    Node* copyRandomList(Node* head) {
        unordered_map<Node*, Node*> m;
        return helper(head, m);
    }
    Node* helper(Node* node, unordered_map<Node*, Node*>& m) {
        if (!node) return nullptr;
        if (m.count(node)) return m[node];
        Node *res = new Node(node->val, nullptr, nullptr);
        m[node] = res;
        res->next = helper(node->next, m);
        res->random = helper(node->random, m);
        return res;
    }
};

方法三:非辅助空间+ O ( n ) O(n) 时间效率+巧妙解法

这个解法在初学链表时给我留下了深刻的印象,没想到在此又遇到这个题,这也是《剑指-Offer》上所推荐的解法。分为三步:

  • 将新复制出来的链表节点就链到原链表节点的后面

  • 在这里插入图片描述

  • 原链表的 random 节点所在位置的 next 就是拷贝链表节点的 random 节点位置

  • 在这里插入图片描述

  • 将这个长链表拆为两个链表,即奇数位置就是拷贝链表

  • 在这里插入图片描述

很经典的思路啊,完美的展现了链表唯一确定下一个节点的特性!

参见代码如下:

// 执行用时 :8 ms, 在所有 C++ 提交中击败了94.82%的用户
// 内存消耗 :13.4 MB, 在所有 C++ 提交中击败了100.00%的用户

/*
// 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 == nullptr) return nullptr;
        Node* cur = head;
        while (cur != nullptr) {
            Node* node = new Node(cur->val);
            node->next = cur->next;
            cur->next = node;
            cur = cur->next->next;
        }
        cur = head;
        while (cur != nullptr) {
            if (cur->random != nullptr) cur->next->random = cur->random->next;
            cur = cur->next->next;
        }
        Node* copyHead = head->next;
        cur = head;
        Node * copy = head->next;
        while (cur != nullptr) {
            cur->next = cur->next->next;
            cur = cur->next;
            if (copy->next != nullptr) {
                copy->next = copy->next->next;
                copy = copy->next;
            }
        }
        return copyHead;
    }
};
发布了307 篇原创文章 · 获赞 125 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/yl_puyu/article/details/104602865