剑指Offer 35.复杂链表的复制(LeetCode 138.复制带随机指针的链表)
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
请实现 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 。
解:
解法1:
最循规蹈矩的做法,也是人能最先想到的做法,便是新建并逐个复制每个结点并链接起来,再遍历得到每个结点的random成员指向的结点在链表中的位置(联想到数组的索引),最后再遍历新的链表,将对应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) return nullptr; int Len = 0,i = 0; Node* copyhead = new Node(head -> val); Node* Nptr = copyhead; const auto const original_head = head; vector<int> rand_idx; //存储每结点random对应位置 while(head) //新建链表 { if(head -> next) Nptr -> next = new Node(head -> next -> val); else Nptr -> next = nullptr; int i = 0; if(head -> random == nullptr) rand_idx.push_back(-1); else if(head -> random == head) //random指向自己 rand_idx.push_back(Len); else { for(auto temp = original_head;temp;temp = temp -> next, ++i)//遍历老链表,得到random指向结点所在位置 { if(temp == head) continue; if(temp == head -> random ) { rand_idx.push_back(i); // cout<<i<<'\t'; break; } } } Len++; Nptr = Nptr -> next; head = head -> next; } i = 0; Nptr = copyhead; while(Nptr) { int j = 0; for(auto temp = copyhead;temp;temp = temp -> next,++j) //按照得到的random位置再次遍历新链表,拿到random地址 { if(rand_idx[i] == -1) break; else if(rand_idx[i] == j) { Nptr->random = temp; break; } } Nptr = Nptr -> next; i++; } return copyhead; } };
此法时间复杂度为O(N2),空间复杂度为O(1),容易看到此方法瓶颈在于为了得到random的索引和random的地址遍历过多。
因此便有了解法2。
解法2:
利用哈希表的查询特点,考虑构建 原链表节点 和 新链表对应节点 的键值对映射关系,再遍历构建新链表各节点的 next 和 random 引用指向即可。
算法流程:
1.若头节点 head 为空节点,直接返回 null;
2.初始化: 哈希表 dic , 节点 cur 指向头节点;
3.复制链表:
建立新节点,并向 dic 添加键值对 (原 cur 节点, 新 cur 节点) ;
cur 遍历至原链表下一节点;
4.构建新链表的引用指向:
构建新节点的 next 和 random 引用指向;
cur 遍历至原链表下一节点;
5.返回值: 新链表的头节点 dic[cur] ;
class Solution { public: Node* copyRandomList(Node* head) { if(head == nullptr) return nullptr; Node* cur = head; unordered_map<Node*, Node*> map; // 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射 while(cur != nullptr) { map[cur] = new Node(cur->val); cur = cur->next; } cur = head; // 4. 构建新链表的 next 和 random 指向 while(cur != nullptr) { map[cur]->next = map[cur->next]; map[cur]->random = map[cur->random]; cur = cur->next; } // 5. 返回新链表的头节点 return map[head]; } }; /* 作者:jyd 链接:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/solution/jian-zhi-offer-35-fu-za-lian-biao-de-fu-zhi-ha-xi-/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 */
复杂度分析:
时间复杂度 O(N) : 两轮遍历链表,使用 O(N)时间。
空间复杂度 O(N): 哈希表 dic 使用线性大小的额外空间。
解法3:
解法2的哈希表额外空间,以空间复杂度O(N)换来了时间复杂度O(N),还可以把空间复杂度降下来。
即原地复制,将复制后的新结点直接连在原结点后。这样random的地址很容易获得,也没占用额外空间。
共进行3次循环。
1.复制结点;
2.得到每个random地址。
3.拆分新链表。
/* // 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; auto Cur = head, Next = head -> next; while(Next) //在每个节点后面复制相同的节点并链接 { Cur -> next = new Node(Cur -> val); Cur -> next -> next = Next; Cur = Next; Next = Cur -> next; } Cur -> next = new Node(Cur -> val); Cur -> next -> next = nullptr; Cur = head; Next = head -> next; //完善上述复制节点的random成员 while(Next -> next) { if(Cur -> random) Next -> random = Cur -> random -> next; Cur = Next -> next; Next = Cur -> next; } if(Cur -> random) Next -> random = Cur -> random -> next; Cur = head; Next = head -> next; //拆分新节点,生成新链表,还原原链表。 auto Newhead = Next; while(Next -> next) { Cur -> next = Next -> next; Next -> next = Cur -> next -> next; Cur = Cur -> next; Next = Next -> next; } Cur -> next = nullptr; return Newhead; } };
复杂度分析:
时间复杂度 O(N): 三轮遍历链表,使用 O(N)时间。
空间复杂度 O(1): 节点引用变量使用常数大小的额外空间。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了