复制含有随机指针节点的链表
【题目】一种特殊的单链表节点类描述如下
class Node {
int value;
Node next;
Node rand;
Node(int val) {
value = val ;
}
}
rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。【要求】时间复杂度O(N),额外空间复杂度0(1)
采用一般的方法直接复制,
问题:因为直接复制next和rand会指向原链表的node地址,而不是新链表的node的地址
方法一(空间复杂度为O(n),笔试中使用能帮助尽快解题):
思想:
- 采用一个Hash表,其key值放老结点的内存地址,value放入从老结点中复制得来的新结点内存地址,当结点遍历完毕后,所有的新旧结点均拷贝完毕;
- 从头遍历给定的但链表,每遍历一个结点,就得出他的rand和next指针,然后因为next指针和rand指针都是作为Hash表key存在,所以可以依据这个next和rand指针经由hash函数得出其对应新结点的所在地址(假设为N和R),再根据当前结点和哈希函数,得出当前结点的新结点,然后再设置新结点的next和rand指针分别指向N和R
- 重复步骤二,直到链表便利完毕
代码
package ListNode;
import Pojo.ListNode;
import java.util.HashMap;
public class CopyNodeWithRand {
static class Node {
int value;
Node next;
Node rand;
Node(int val) {
value = val ;
}
}
public static void main(String[] args) {
}
public static Node copyNodeWithRand(Node head)
{
HashMap<Node, Node> map = new HashMap<>();
Node cur = head;
// 将链表所有结点的值放到其对应的新结点中并且将这个新结点作为hashMap的value,旧结点作为map的key
while (cur != null)
{
map.put(cur, new Node(cur.value));
cur = cur.next;
}
cur =head;
while (cur != null)
{
// 设置当前结点对应新结点的random和next指针
map.get(cur).next = map.get(cur.next);
map.get(cur).rand = map.get(cur.rand);
}
return map.get(head);
}
}
方法二(空间复杂度为O(1),面试中使用能体现出你的水平)
思想:
该思想主要运用新旧节点之间的位置关系省略了方法一中的额外的hash表空间
- 为每一个结点生成一个克隆结点,克隆结点只包含原结点的data值,将其作为对应老结点的下一个结点。比如对于链表 1 -> 2 -> 3 -> null,其中1的rand域指向3,2的rand域指向1;按照该规则,链表则变化为 1 -> 1’ -> 2 -> 2’ -> 3 -> 3’ -> null;
- 采用双指针pre和cur,pre始终指向cur的前一个结点,cur为当前遍历结点,当cur遍历到克隆节点时,比如1’,则pre指向该克隆节点的原结点,此时通过pre.rand.next即可得到cur结点(即1’)的rand指针指向的地址,随后cur跳到下一个旧结点(指2),如此直到遍历到链表空结点。
- 将新的链表进行撕裂,分成两个链表,一个全部由新结点组成,另一个全部由旧结点组成,然后返回全部由克隆节点组成的链表表头
代码:
// 不使用额外空间,只利用链表新老结点之间的顺序特征进行复制
public static Node copyNodeWithRand2(Node head)
{
Node cur =head;
// 为每一个结点生成一个克隆结点,克隆结点只包含原结点的data值,将其作为对应老结点的下一个结点。
// 比如对于链表 1 -> 2 -> 3 -> null,其中1的rand域指向3,2的rand域指向1;按照该规则,
// 链表则变化为 1 -> 1' -> 2 -> 2' -> 3 -> 3' -> null
while (cur != null)
{
Node next = cur.next;
cur.next = new Node(cur.value);
cur.next.next = next;
cur = cur.next;
}
// copy rand pointer of each old node
cur = head;
while (cur!= null)
{
Node next_next = cur.next.next;// cur每次跳两步
Node curCopy = cur.next;
curCopy.rand = cur.rand != null ? cur.rand.next : null;
cur = next_next;
}
// split
cur = head;
Node response = head.next;
while (cur != null)
{
Node next_next = cur.next.next; // cur每次跳两步
Node curCopy = cur.next;
curCopy.next = next_next.next; // 新结点连接到新结点
cur.next = next_next != null ? next_next : null; // 旧结点连接到旧结点
cur = cur.next; // 遍历下一个旧结点
}
return response;
}