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

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;
    }
}

你可能感兴趣的:(剑指offer,剑指offer)