剑指offer第二版-35.复杂链表的复制

本系列导航:剑指offer(第二版)java实现导航帖

面试题35:复杂链表的复制
题目要求:在复杂链表中,每个节点除了有一个next指针指向下一个节点,还有一个random指针指向链表中的任意节点或null,请完成一个能够复制复杂链表的函数。

解题思路:
此题定义了一种新的数据结构,复杂链表。与传统链表的区别是多了一个random指针。本题的关键点也就在如何高效地完成random指针的复制。

解法 时间复杂度 空间复杂度
解法一 o(n^2) o(1)
解法二 o(n) o(n)
解法三 o(n) o(1)

解法一比较直接:
先把这个复杂链表当做传统链表对待,只复制val域与next域(时间o(n)),再从新链表的头部开始,对random域赋值(时间o(n^2))。

package chapter4;
import java.util.HashMap;

/**
 * Created by ryder on 2017/7/18.
 * 复制复杂链表
 */
public class P187_CopyComplexList {
    public static class ComplexListNode{
        int val;
        ComplexListNode next;
        ComplexListNode random;

        public ComplexListNode(int val) {
            this.val = val;
        }
        @Override
        public String toString() {
            StringBuilder ret = new StringBuilder();
            ComplexListNode cur = this;
            while(cur!=null) {
                ret.append(cur.val);
                if(cur.random!=null)
                    ret.append("("+cur.random.val+")");
                else{
                    ret.append("(_)");
                }
                ret.append('\t');
                cur = cur.next;
            }
            return ret.toString();
        }
    }
    //解法一
    //time:o(n^2) space:o(1) 新链表使用的n个长度的空间不算入
    //先复制val与next(时间o(n)),再复制random域(时间o(n^2))
    public static ComplexListNode clone1(ComplexListNode head){
        if(head==null)
            return null;
        ComplexListNode newHead = new ComplexListNode(head.val);
        ComplexListNode cur = head.next;
        ComplexListNode newCur = null;
        ComplexListNode newCurPrev = newHead;
        while (cur!=null){
            newCur = new ComplexListNode(cur.val);
            newCurPrev.next = newCur;
            newCurPrev = newCurPrev.next;
            cur = cur.next;
        }
        cur = head;
        newCur = newHead;
        ComplexListNode temp = head;
        ComplexListNode newTemp = newHead;
        while(cur!=null){
            if(cur.random!=null){
                temp = head;
                newTemp = newHead;
                while (temp!=cur.random){
                    temp = temp.next;
                    newTemp = newTemp.next;
                }
                newCur.random = newTemp;
            }
            cur = cur.next;
            newCur = newCur.next;
        }
        return newHead;
    }
    public static void main(String[] args){
        ComplexListNode head = new ComplexListNode(1);
        ComplexListNode c2 = new ComplexListNode(2);
        ComplexListNode c3 = new ComplexListNode(3);
        ComplexListNode c4 = new ComplexListNode(4);
        ComplexListNode c5 = new ComplexListNode(5);
        head.next = c2;
        head.random = c3;
        head.next.next = c3;
        head.next.random = c5;
        head.next.next.next = c4;
        head.next.next.next.next = c5;
        head.next.next.next.random = c2;
        System.out.println("original:"+'\t'+head);
        System.out.println("clone1:  "+'\t'+clone1(head));
        System.out.println("clone2:  "+'\t'+clone2(head));
        System.out.println("clone3:  "+'\t'+clone3(head));
    }
}

解法二是用空间换时间:
解法一时间复杂度高的原因在于确定random域的费时,即假设原链表第m个节点指向第k个节点,而在新链表的第m个节点处无法直接得到第k个节点,需从头遍历。很自然的想法是用一个哈希表记录旧链表每个节点到新链表对应节点的映射,从而可以将时间复杂度降低为o(n)。

    //解法二
    //time:o(n) space:o(n)
    //使用o(n)的空间,换取了时间复杂度的降低
    public static ComplexListNode clone2(ComplexListNode head) {
        if(head==null)
            return null;
        HashMap<ComplexListNode,ComplexListNode> oldToNew = new HashMap<>();
        ComplexListNode newHead = new ComplexListNode(head.val);
        oldToNew.put(head,newHead);
        ComplexListNode cur = head.next;
        ComplexListNode newCur = null;
        ComplexListNode newCurPrev = newHead;
        while (cur!=null){
            newCur = new ComplexListNode(cur.val);
            oldToNew.put(cur,newCur);
            newCurPrev.next = newCur;
            newCurPrev = newCurPrev.next;
            cur = cur.next;
        }
        cur = head;
        newCur = newHead;
        while(cur!=null){
            if(cur.random!=null){
                newCur.random = oldToNew.get(cur.random);
            }
            cur = cur.next;
            newCur = newCur.next;
        }
        return newHead;
    }

解法三:
思路很巧妙。将复制的任务分为如下三个部分:
1)cloneNodes完成新链表节点的创建,仅对val域赋值,且每个新节点接在原链表对应节点的后面。如A->B->C,处理完后为A->A'->B->B'->C->C',时间复杂度o(n)。
2)connectRandomNode完成random域的赋值。假设A.random=C,我们需要设置A'.random=C',此处获取C'可以在o(1)的时间复杂度完成,全部赋值完毕时间复杂度为o(n)。
3)reconnectNodes就是将上述链表重组,使A->A'->B->B'->C->C'变为A->B->C,A'->B'->C'。此处需要注意尾部null的处理。

    //解法三
    //time:o(n) space:o(1)
    public static ComplexListNode clone3(ComplexListNode head) {
        if(head==null)
            return null;
        cloneNodes(head);
        connectRandomNodes(head);
        return reconnectNodes(head);
    }
    public static void cloneNodes(ComplexListNode head){
        ComplexListNode cur = head;
        ComplexListNode temp = null;
        while (cur!=null){
            temp = new ComplexListNode(cur.val);
            temp.next = cur.next;
            cur.next = temp;
            cur = cur.next.next;
        }
    }
    public static void connectRandomNodes(ComplexListNode head){
        ComplexListNode cur = head;
        ComplexListNode curNext = head.next;
        while (true){
            if(cur.random!=null)
                curNext.random = cur.random.next;
            cur = cur.next.next;
            if(cur == null)
                break;
            curNext = curNext.next.next;
        }
    }
    public static ComplexListNode reconnectNodes(ComplexListNode head){
        ComplexListNode newHead = head.next;
        ComplexListNode cur = head;
        ComplexListNode newCur = newHead;
        while (true){
            cur.next = cur.next.next;
            cur = cur.next;
            if(cur==null){
                newCur.next = null;
                break;
            }
            newCur.next = newCur.next.next;
            newCur = newCur.next;
        }
        return newHead;
    }

运行结果

original:   1(3)    2(5)    3(_)    4(2)    5(_)    
clone1:     1(3)    2(5)    3(_)    4(2)    5(_)    
clone2:     1(3)    2(5)    3(_)    4(2)    5(_)    
clone3:     1(3)    2(5)    3(_)    4(2)    5(_)    

推荐阅读更多精彩内容

  • 总结 想清楚再编码 分析方法:举例子、画图 第1节:画图分析方法 对于二叉树、二维数组、链表等问题,都可以采用画图...
    M_巴拉巴拉阅读 914评论 0 7
  • 转载请注明出处:http://www.jianshu.com/p/c65d9d753c31 在上一篇博客《数据结构...
    Alent阅读 2,940评论 3 73
  • //leetcode中还有花样链表题,这里几个例子,冰山一角 求单链表中结点的个数----时间复杂度O(n)这是最...
    暗黑破坏球嘿哈阅读 811评论 0 6
  • 辟谷日记:当身体自己不想吃东西的时候开始辟谷,但是不断食,辟谷只是不吃五谷杂粮但是蔬菜和水果还是想吃的。第一天辟谷...
    偶然来到的猫阅读 136评论 0 0
  • 夜之旅,我们相识在2014.3.18日。
    鲋鱼阅读 26评论 0 0