剑指offer 复杂链表赋值Java
题目链接
1.普通链表的定义:
class Node{
int val;
Node next;
public Node(int val){
this.val = val;
this.next = null;
}
2.本题的复杂链表节点定义:
class Node{
int val;
Node next,random;
public Node(int val){
this.val = val;
this.next = null;
this.random = null;
}
}
给定链表的头节点 head ,复制普通链表很简单,只需遍历链表,每轮建立新节点 + 构建前驱节点 pre 和当前节点 node 的引用指向即可。
本题链表的节点新增了 random 指针,指向链表中的 任意节点 或者 null 。这个 random 指针意味着在复制过程中,除了构建前驱节点和当前节点的引用指向 pre.next ,还要构建前驱节点和其随机节点的引用指向 pre.random 。
普通的链表复制
class Solution {
public Node copyRandomList(Node head) {
Node cur = head;
Node dum = new Node(0), pre = dum;
while(cur != null) {
Node node = new Node(cur.val); // 复制节点 cur
pre.next = node; // 新链表的 前驱节点 -> 当前节点
// pre.random = "???"; // 新链表的 「 前驱节点 -> 当前节点 」 无法确定
cur = cur.next; // 遍历下一节点
pre = node; // 保存当前新节点
}
return dum.next;
}
}
方法一:哈希表
利用哈希表的查询特点,考虑构建 原链表节点 和 新链表对应节点 的键值对映射关系,再遍历构建新链表各节点的 next 和 random 引用指向即可。
算法流程:
若头节点 head 为空节点,直接返回 null ;
初始化: 哈希表 dic , 节点 cur 指向头节点;
复制链表:
建立新节点,并向 dic 添加键值对 (原 cur 节点, 新 cur 节点) ;
cur 遍历至原链表下一节点;
构建新链表的引用指向:
构建新节点的 next 和 random 引用指向;
cur 遍历至原链表下一节点;
返回值: 新链表的头节点 dic[cur] ;
复杂度分析:
时间复杂度 O(N)O(N) : 两轮遍历链表,使用 O(N)O(N) 时间。
空间复杂度 O(N)O(N) : 哈希表 dic 使用线性大小的额外空间。
class Solution {
public Node copyRandomList(Node head) {
//1.判断头节点是否为空
if(head == null) return null;
//2.定义cur并赋值为头节点
Node cur = head;
//3.初始化哈希表
Map<Node,Node> map = new HashMap<>();
//4.复制各节点,并建立"原节点->新节点"的Map映射
while(cur!=null){
map.put(cur,new Node(cur.val));
cur = cur.next;
}
//5.让cur重新等于head节点
cur = head;
//6.构建新链表的next,random指向
while(cur!=null){
map.get(cur).next=map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
return map.get(head);
}
}
方法二:拼接 + 拆分
考虑构建 原节点 1 -> 新节点 1 -> 原节点 2 -> 新节点 2 -> …… 的拼接链表,如此便可在访问原节点的 random 指向节点的同时找到新对应新节点的 random 指向节点。
算法流程:
复制各节点,构建拼接链表:
设原链表为 node1 \rightarrow node2 \rightarrow \cdotsnode1→node2→⋯ ,构建的拼接链表如下所示:
node1 \rightarrow node1_{new} \rightarrow node2 \rightarrow node2_{new} \rightarrow \cdots
node1→node1
new
→node2→node2
new
→⋯
构建新链表各节点的 random 指向:
当访问原节点 cur 的随机指向节点 cur.random 时,对应新节点 cur.next 的随机指向节点为 cur.random.next 。
拆分原 / 新链表:
设置 pre / cur 分别指向原 / 新链表头节点,遍历执行 pre.next = pre.next.next 和 cur.next = cur.next.next 将两链表拆分开。
返回新链表的头节点 res 即可。
复杂度分析:
时间复杂度 O(N)O(N) : 三轮遍历链表,使用 O(N)O(N) 时间。
空间复杂度 O(1)O(1) : 节点引用变量使用常数大小的额外空间。
class Solution {
public Node copyRandomList(Node head) {
if(head == null) return null;
Node cur = head;
// 1. 复制各节点,并构建拼接链表
while(cur != null) {
Node tmp = new Node(cur.val);
tmp.next = cur.next;
cur.next = tmp;
cur = tmp.next;
}
// 2. 构建各新节点的 random 指向
cur = head;
while(cur != null) {
if(cur.random != null)
cur.next.random = cur.random.next;
cur = cur.next.next;
}
// 3. 拆分两链表
cur = head.next;
Node pre = head, res = head.next;
while(cur.next != null) {
pre.next = pre.next.next;
cur.next = cur.next.next;
pre = pre.next;
cur = cur.next;
}
pre.next = null; // 单独处理原链表尾节点
return res; // 返回新链表头节点
}
}
本文来自博客园,作者:蹇爱黄,转载请注明原文链接:https://www.cnblogs.com/jianjiana/p/15860731.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?