​welcome to my blog​

剑指offer面试题35(java版):复杂链表的复制

笔记

  • 这道题卡了很久, 因为最后拆分链表的时候, 我只提取了复制后的链表, 没有管原先的链表, 结果就判错了
  • 是不是判题的机制有问题?
  • 注意random为null的情况, 此时random没有next. even.random = odd.random == null? null: odd.random.next;

第四次做; 核心: 1)克隆每个节点,并将其接在原始节点的后面 2)处理random指针 3)拆链

class Solution {
public Node copyRandomList(Node head) {
if(head==null)
return null;
Node cur = head, right;
while(cur!=null){
//save next
right = cur.next;
//
cur.next = new Node(cur.val);
cur.next.next=right;
//update
cur = right;
}
//处理random指针
cur = head;
while(cur!=null){
cur.next.random = cur.random==null? null : cur.random.next;
cur = cur.next.next;
}
//拆链; 一组一组地处理
cur=head;
right=head.next;
Node newHead = head.next;
while(cur!=null){
cur.next = cur.next.next;
right.next = right.next==null? null : right.next.next;
//update
cur = cur.next;
right = right.next;
}
return newHead;
}
}

第四次做; 核心: 1)使用哈希表记录新旧节点的对应关系 2)注意最后一个节点的处理

class Solution {
public Node copyRandomList(Node head) {
if(head==null)
return null;
HashMap<Node, Node> map = new HashMap<>();
Node head2 = new Node(head.val);
Node cur1=head,cur2=head2;
while(cur1!=null){
cur2.next = cur1.next==null? null : new Node(cur1.next.val);
map.put(cur1, cur2);
//update
cur1=cur1.next;
cur2=cur2.next;
}
//处理random指向null的情况; 不处理应该也行, 因为map.get()获取不到元素会返回null
// map.put(null, null);
//处理random指针
cur1=head;
cur2=head2;
while(cur1!=null){
cur2.random = map.get(cur1.random);
//update
cur1 = cur1.next;
cur2 = cur2.next;
}
return head2;
}
}

思路

  • 三步走:
  1. 根据原始链表的每个节点N创建对应的N’, N’连接在N的后面
  2. 设置复制出来的节点的 random, 假设原始链表上的N的random指向S, 那么复制出来的N’是N指向的节点, 同样,S’是S指向的节点
  3. 把这个长链表拆分成两个链表: 把奇数位置的节点连起来就是原始链表, 把偶数位置的节点连起来就是复制出来的链表

第三次做; 拆链过程:只更新一个指针, 用一个指针表示另一个指针; 拆链过程: 如何避免null异常? 看代码

public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead==null)
return null;
//每个节点后面加上一个新节点
RandomListNode curr=pHead,temp,right;
while(curr!=null){
//
right = curr.next;
//
curr.next = new RandomListNode(curr.label);
curr.next.next = right;
//
curr = right;
}
//赋值random指针
curr=pHead;
while(curr!=null){
//
right = curr.next.next;
//
curr.next.random = curr.random==null? null : curr.random.next;
//
curr = right;
}
//拆链
curr=pHead;
RandomListNode newHead = curr.next;
while(curr!=null){
//
right = curr.next.next;
temp = curr.next;
//@#@#@#
curr.next = right;
temp.next = right==null? null:right.next;
//
curr = right;
}
return newHead;
}
}

第二遍做, 为防止出现null.next的错误,后两个while循环中加上了if;实际上可以不用加if,因为我使用的两个指针是相邻的,所以只更新一个指针即可,另一个指针用该指针进行表示

  • //right = curr.next.next;//不涉及断链操作不用保存下一个节点
  • left.random==null?null:left.random.next; //避免出现null.next的错误
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead == null)
return null;
//
RandomListNode left=null, curr=pHead, right=null;
RandomListNode duplicate = null;
//第一遍遍历链表,不处理特殊指针
while(curr!=null){
//save next node
right = curr.next;
//execute
duplicate = new RandomListNode(curr.label);
curr.next = duplicate;
duplicate.next = right;
curr = right;
}
//第二遍遍历链表,处理特殊指针
left = pHead;
curr = pHead.next;//指向复制的节点
while(curr!=null){//不能把while改成while(curr.next!=null),这样就不处理最后一个节点的特殊指针了
//save next next node
//right = curr.next.next;//不涉及断链操作不用保存下一个节点
//execute
curr.random = left.random==null?null:left.random.next;//格外注意null的情况
//update
//相当于越界检查。
if(curr.next==null)//为什么在这里加if?画个图更好理解
break;
left = left.next.next;
curr = curr.next.next;
}
//将原链表和复制的链表拆开
left=pHead;
curr=pHead.next;
right=pHead.next;//保存复制链表的头节点
while(curr!=null){ //不能用while(curr.next!=null),这样写会少处理一个
if(curr.next==null){
left.next = null;
break;
}
left.next = left.next.next;
curr.next = curr.next.next;
//update. caution! 不是left.next.next了!上两句已经改变了链表结构
left = left.next;
curr = curr.next;
}
return right;
}
}
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
/*
三步走:
1. 根据原始链表的每个节点N创建对应的N', N'连接在N的后面
2. 设置复制出来的节点的 random, 假设原始链表上的N的random指向S, 那么复制出来的N'是N指向的节点, 同样,S'是S指向的节点
3. 把这个长链表拆分成两个链表: 把奇数位置的节点连起来就是原始链表, 把偶数位置的节点连起来就是复制出来的链表
*/
// input check
if(pHead == null)
return null;
// execute
//1.
// key: 灵活使用两个指针; 注意好边界处的处理
// 为每个节点创建一个新节点, 并且原始节点指向新节点
RandomListNode curr = pHead;
RandomListNode temp = null;
while(curr != null){
RandomListNode newNode = new RandomListNode(curr.label);
temp = curr.next; // 暂存当前节点的下一个节点
curr.next = newNode; // 原始节点指向复制出来的节点
newNode.next = temp; // 复制的节点指向暂存的节点
curr = temp; // curr后移
}
//2.
// key:创建两个指针odd, even, 其中even在while循环内部根据odd进行赋值(一外一内原则). 并不是在while循环内部单独更新odd和even两个
// 奇偶位置的两个节点为一组(复制后的链表至少有两个节点)
RandomListNode odd = pHead;
RandomListNode even = null;
while(odd != null){
even = odd.next; // 循环条件中是谁, 就用谁的next. 而不是在while内部同时更新odd和even, 容易出现null.next的错误
//even.random = odd.random.next; // N的random的next就是N'的random // 忽略了odd.random == null的情况
even.random = odd.random == null? null: odd.random.next;
odd = even.next; // 奇偶位置的节点作为一组, 最后一组中奇数位置的节点经过两个next后才是null(要及时发现这一点). 前面已经使用了一个next, 这里是第二个next
}
//3.
// 奇连奇, 偶连偶
curr = pHead.next;
RandomListNode newHead = pHead.next;
RandomListNode pCurr = pHead;
while(curr != null){ //curr.next != null意味着可以使用curr.next.next
pCurr.next = pCurr.next.next;
curr.next = curr.next==null? null:curr.next.next; // 等号右边的先执行. 偶连偶
curr = curr.next; // 后移curr
pCurr = pCurr.next;
}
return newHead;
}
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;

RandomListNode(int label) {
this.label = label;
}
}
}
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
/*
三步走:
1. 根据原始链表的每个节点N创建对应的N', N'连接在N的后面
2. 设置复制出来的节点de random, 假设原始链表上的N的random指向S, 那么复制出来的N'是N指向的节点, 同样,S'是S指向的节点
3. 把这个长链表拆分成两个链表: 把奇数位置的节点连起来就是元是链表, 把偶数位置的节点连起来就是复制出来的链表
*/
// input check
if(pHead == null)
return null;
// execute
//1.
CloneNodes(pHead);
//2.
ConnectRandomNodes(pHead);
//3.
return ReconnectNodes(pHead);
}
//1.
public void CloneNodes(RandomListNode pHead){
// key: 灵活使用两个指针; 注意好边界处的处理
// 为每个节点创建一个新节点, 并且原始节点指向新节点
RandomListNode curr = pHead;
RandomListNode temp = null;
while(curr != null){
RandomListNode newNode = new RandomListNode(curr.label);
temp = curr.next; // 暂存当前节点的下一个节点
curr.next = newNode; // 原始节点指向复制出来的节点
newNode.next = temp; // 复制的节点指向暂存的节点
curr = temp; // curr后移
}
}
//2.
public void ConnectRandomNodes(RandomListNode pHead){
// key:创建两个指针odd, even, 其中even在while循环内部根据odd进行赋值(一外一内原则). 并不是在while循环内部单独更新odd和even两个
// 奇偶位置的两个节点为一组(复制后的链表至少有两个节点)
RandomListNode odd = pHead;
RandomListNode even = null;
while(odd != null){
even = odd.next; // 循环条件中是谁, 就用谁的next. 而不是在while内部同时更新odd和even, 容易出现null.next的错误
//even.random = odd.random.next; // N的random的next就是N'的random // 忽略了odd.random == null的情况
even.random = odd.random == null? null: odd.random.next;
odd = even.next; // 奇偶位置的节点作为一组, 最后一组中奇数位置的节点经过两个next后才是null(要及时发现这一点). 前面已经使用了一个next, 这里是第二个next
}
}
//3.
public RandomListNode ReconnectNodes(RandomListNode pHead){
// 奇连奇, 偶连偶
RandomListNode curr = pHead.next;
RandomListNode newHead = pHead.next;
RandomListNode pCurr = pHead;
while(curr != null){ //curr.next != null意味着可以使用curr.next.next
pCurr.next = pCurr.next.next;
curr.next = curr.next==null? null:curr.next.next; // 等号右边的先执行. 偶连偶
curr = curr.next; // 后移curr
pCurr = pCurr.next;
}
return newHead;
}
}