复制带有随机指针rand的链表
提示:这个跟单独复制一个链表有区别
文章目录
题目
给你一个链表,链表有next指针,这是主链,当然也有rand指针,但是rand指针可能是随机指向null或者其他某个点的。
请你复制带有rand指针的链表,返回复制链表的头节点。
一、审题
示例:下图总的链表,除了next把1-2-3-4串接起来了
1节点的rand指向了3节点
2节点的rand指向了自己
3和4的rand指向了null
请你复制一个同样的链表,然后返回新链表的头节点。
二、解题
又是一个链表的题目,链表是整个都要遍历的o(n)时间复杂度逃不了,唯独就是看看能否优化空间复杂度
本题涉及的链表的节点,数据结构Node多了rand
public static class Node{
public int value;
public Node next;
public Node rand;
public Node(int v){
value = v;
}
}
造出一根链表来:
//复习:造链表
public static Node createLinkNode(){
Node n1 = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
n1.next = n2;
n1.rand = null;
n2.next = n3;
n2.rand = n1;
n3.rand = n1;
return n1;
}
造出来的链表是这样的:
1
其rand指针为:null
2
其rand指针为:1
3
其rand指针为:1
笔试求AC,耗费额外空间复杂度o(n)
既然是复制,那就可以额外用空间,先复制相同的节点,然后再套rand指针
这里的话,我们得知道每个节点cur,它next连着谁?也要直到rand它连着谁?
故复制出来一个新的节点,咱得知道旧节点都得情况
(1)所以建议用哈希表存好新旧节点之间的对应关系
通过旧节点访问其next和rand指针,来搞定新节点
哈希表中:key:旧节点,value:新节点
(2)这样的话,遍历原链表,拿到旧节点key,就能通过get函数拿到新节点,旧节点怎么连next和rand指针,新节点就怎么连其next和rand指针。
比如:上面的1节点,新1的next应该指向谁呢?当然是旧1的next指向的旧2点,所对应的新2点
代码这么描述:map.get(旧1点).next 【新1点】= map.get(旧1.next【即旧2点】) 【即新2点】;
上图绿色2就是新2点,粉色3就是新3点
总之,新点的连接方式,全靠旧点的连接指引
手撕代码:
//复习:笔试AC代码,哈希表
//所以建议用哈希表存好新旧节点之间的对应关系
//通过旧节点访问其next和rand指针,来搞定新节点
//哈希表中:key:旧节点,value:新节点
public static Node copyPen(Node head){
if (head == null) return head;
//复制一下:哈希表中:key:旧节点,value:新节点
HashMap<Node, Node> map = new HashMap<>();
Node cur = head;
while (cur != null){
map.put(cur, new Node(cur.value));
cur = cur.next;
}
//(2)这样的话,遍历原链表,拿到旧节点key,就能通过get函数拿到新节点,
// 旧节点怎么连next和rand指针,新节点就怎么连其next和rand指针。
cur = head;
while (cur != null){
map.get(cur).next = map.get(cur.next);//新节点next,指向旧节点cur的下一个点,对应的复制节点
map.get(cur).rand = map.get(cur.rand);//同理,rand也是根据旧节点rand指向那个点的复制节点
cur = cur.next;
}
return map.get(head);//旧链表头对应的复制节点,就是新链表
}
面试求最优解,优化空间复杂度o(1)
面试就要节约空间,争取做到o(1)
那么就只能在原链表上操作了
这样做:
(1)直接在原链表每一个节点cur后面复制一个节点copyCur,当然,先要记好,下次操作的节点next=cur.next;
(2)让cur的next断开,指向新节点copyCur,然后copyCur的next指向next节点。
(3)整个链表都复制好以后,检查新节点的rand指针,原链表的rand指向哪里,新节点的rand就指向原链表对应复制节点的rand那。
(4)然后将新旧链表断开,各自连各自的
看图
(1)(2)(3)两步整好以后:
(4)之后:各自断开,红色那条就是旧链表,粉色那条就是新链表。
好,手撕代码,链表的题目就是coding要仔细
//复习:面试代码:
//(1)直接在原链表每一个节点cur后面复制一个节点copyCur,当然,先要记好,下次操作的节点next=cur.next;
//(2)让cur的next断开,指向新节点copyCur,然后copyCur的next指向next节点。
//(3)整个链表都复制好以后,检查新节点的rand指针,原链表的rand指向哪里,新节点的rand就指向原链表对应复制节点的rand那。
//(4)然后将新旧链表断开,各自连各自的
public static Node copyFace(Node head){
if (head == null) return head;
//(1)直接在原链表每一个节点cur后面复制一个节点copyCur,当然,先要记好,下次操作的节点next=cur.next;
//(2)让cur的next断开,指向新节点copyCur,然后copyCur的next指向next节点。
Node cur = head;
Node next = cur.next;//下次要去的地方
while (cur != null){
Node copyNode = new Node(cur.value);//复制cur
cur.next = copyNode;//断开,重新指新节点
copyNode.next = next;//中间插入一个,挂好旧节点
cur = next;
if (cur != null) next = cur.next;//操作下一个
}
//(3)整个链表都复制好以后,检查新节点的rand指针,原链表的rand指向哪里,
// 新节点的rand就指向原链表对应复制节点的rand那。
cur = head;
next = cur.next.next;//跳两步
while (cur != null){
Node copyNode = cur.next;
copyNode.rand = cur.rand == null ? null : cur.rand.next;//旧节点rand所指的next是复制节点
cur = next;//操作下一个旧节点
if (cur != null) next = cur.next.next;//跳两步
}
//(4)然后将新旧链表断开,各自连各自的
cur = head;
next = cur.next.next;//跳两步
Node ans = cur.next;//新链表的头
while (cur != null){
Node copyNode = cur.next;
cur.next = next;//断开,跳指旧节点
if (next != null) copyNode.next = next.next;//跳指,新节点
cur = next;//操作下一个
if (cur != null) next = cur.next.next;
}
//当中途cur或者next有遇到null,他们是没指针的。
return ans;
}
代码是复杂了点,但是空间复杂度o(1)必然带来复杂的操作程序
测试一下:
//测试
public static void test2(){
Node head = createLinkNode();
Node cur = copyPen(head);
while (cur != null){
System.out.println(cur.value);
if (cur.rand == null) System.out.println("其rand指针为:null");
else System.out.println("其rand指针为:" + cur.rand.value);
cur = cur.next;
}
System.out.println("---------------");
Node head2 = createLinkNode();
Node cur2 = copyFace(head2);
while (cur2 != null){
System.out.println(cur2.value);
if (cur2.rand == null) System.out.println("其rand指针为:null");
else System.out.println("其rand指针为:" + cur2.rand.value);
cur2 = cur2.next;
}
}
public static void main(String[] args){
// test();
test2();
}
俩代码的结果都没问题:
1
其rand指针为:null
2
其rand指针为:1
3
其rand指针为:1
---------------
1
其rand指针为:null
2
其rand指针为:1
3
其rand指针为:1
总结
提示:重要经验:
1)关于链表的操作代码,思想很简单,主要就是coding复杂了点,把细节抠清楚,才行
2)将节点复制,挂中中间,又断开,就需要cur和next配合,这是链表操作行为中的常规骚操作。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。