复杂链表的复制

一、题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

复杂链表的定义:

public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}

下图是一个含有5个结点的复杂链表。图中实线箭头表示Next指针,虚线箭头表示random指针。为简单起见,指向NULL的指针没有画出。

 

                                   

二、解题思路

2.1 O(n2)的普通解法

  第一步是复制原始链表上的每一个结点,并用Next节点链接起来;

  第二步是设置每个结点的random节点指针。

对于一个含有n个结点的链表,由于定位每个结点的random都需要从链表头结点开始经过O(n)步才能找到,因此这种方法的总时间复杂度是O(n2)

 

2.2 借助辅助空间的O(n)解法

  第一步仍然是复制原始链表上的每个结点N创建N',然后把这些创建出来的结点用Next链接起来。同时我们把<N,N'>的配对信息放到一个哈希表中。

  第二步还是设置复制链表上每个结点的random。由于有了哈希表,我们可以用O(1)的时间根据S找到S'

此方法使用空间换时间。对于有n个结点的链表我们需要一个大小为O(n)的哈希表,也就是说我们以O(n)的空间消耗把时间复杂度由O(n2)降低到O(n)

2.3 不借助辅助空间的O(n)解法

第一步仍然是根据原始链表的每个结点N创建对应的N'。(把N'链接在N的后面)

第一步 在原链表每个结点后面分别复制创建新的结点 

//第一步 在原链表每个结点后面分别复制创建新的结点
//下面注释仅表示如何使A->B变成  A->A'->B->B',并且pNode 指向A变成pNode指向B
    public static void cloneNodes(RandomListNode pHead) {
        RandomListNode pNode = pHead;
        while (pNode != null) {
            RandomListNode  pCloned = new RandomListNode(0);
            pCloned.label = pNode.label;
            pCloned.next = pNode.next;//A'->B 
            pCloned.random = null;

            pNode.next = pCloned;//A->A'

            pNode = pCloned.next;// 引用(指针)pNode指向节点B
        }

说明:1)pNode = pCloned.next, 下面说明

  /**
             * 此处可以把pNode 理解成指向头节点的引用(指针)变量
             * pNode = pCloned.next 表示把引用变量pNode指向pCloned指向节点的下一个节点
             * 而pNode 原来节点的位置不发生变化
             * 另外,这个链表依然有一个头指针pHead,它并没没有消失,RandomListNode pNode = pHead;
             * 所以虽然引用变量pNode 往后面移动,但最开始引用变量指向的节点并没有变化。
             * 所以,这个链表最终可以用pHead 进行表示。
             */   可以参考:https://blog.csdn.net/nsjlive/article/details/88032748 (链表的节点与指针)         

pNode = pCloned.next;// pNode和pCloned都是节点的引用,指向相应的节点。

 2)pCloned.next = pNode.next; 下面解释

//若pNode指向A,,pNode.next表示A的下一个节点,假设该节点是B。

若pCloned指向的节点为A', 现在使pCloned.next==B,表示节点A'下一个节点是B

//第一步 在原链表每个结点后面分别复制创建新的结点
//下面注释仅表示如何使A->B变成  A->A'->B->B',并且pNode 指向A变成pNode指向B
    public static void cloneNodes(RandomListNode pHead) {
        RandomListNode pNode = pHead;
        while (pNode != null) {
            RandomListNode  pCloned = new RandomListNode(0);
            pCloned.label = pNode.label;
            pCloned.next = pNode.next;//A'->B 
            pCloned.random = null;

            pNode.next = pCloned;//A->A'

            pNode = pCloned.next;// 引用(指针)pNode指向节点B
        }

第二步设置复制出来的结点的random。

(把N'的random指向N的rabdom.next)

 

 //第二步 根据旧链表的兄弟结点 初始化新链表的兄弟结点
    //下面 假设初始pNode指向节点A 仅说明节点A 与B及其随机指针指向节点的关系
    public static void connectRandom(RandomListNode pHead) {
        RandomListNode pNode = pHead;
        while (pNode != null) {
            RandomListNode pCloned = pNode.next;//pCloned 指向A’
            /**
             * 若A.random存在
             * 则A'.random也一定存在。则A'的随机(random)节点为A的随机节点的下一个节点
             */
            if (pNode.random != null) {
                pCloned.random = pNode.random.next;
            }
            pNode = pCloned.next;//引用pNode移动到B节点 ,然后从B节点开始一直往后面......
        }
    }

第三步把这个长链表拆分成两个链表

把奇数位置的结点用Next链接起来就是原始链表,偶数数值的则是复制链表。

 //第三步 从旧链表拆分得到新的结点
    public static RandomListNode reconnectNode(RandomListNode pHead) {
        RandomListNode pNode = pHead;
        RandomListNode pClonedHead = null;
        RandomListNode pClonedNode = null;

        if (pNode != null) {//此处并非循环,仅执行一次
            pClonedHead = pClonedNode = pNode.next;//引用  pClonedHead 和 pClonedNode指向A'
            pNode.next = pClonedNode.next;//A的下一个节点是B
            pNode = pNode.next;// 引用pNode指向B
        }

        while (pNode != null) {

            // 复制链表的连接
            pClonedNode.next = pNode.next;//A'的下一个节点是B'(也可以说A'指向B’),指向的同时断开A'与B的联系
            pClonedNode = pClonedNode.next;// pClonedNode指向B'

            // 原始链表的连接
            pNode.next = pClonedNode.next;//节点B的下一个节点是C
            pNode = pNode.next;//引用pNode指向C
        }
        return pClonedHead;
    }

 最后,将三个步骤衔接起来形成Clone方法:

 //分为三个步骤
    //第一步 在原链表每个结点后面分别复制创建新的结点
    //第二步 根据旧链表的兄弟结点 初始化新链表的兄弟结点
    //第三步 从旧链表拆分得到新的结点
    public static RandomListNode Clone(RandomListNode pHead) {
        if(pHead==null) return null;//最好加上这一条语句,不然有可能case通过率为80% 
        cloneNodes(pHead);
        connectRandom(pHead);
        return reconnectNode(pHead);
    }

完整代码汇总:




public class Test {
    //第一步 在原链表每个结点后面分别复制创建新的结点
    public static void cloneNodes(RandomListNode pHead) {
        RandomListNode pNode = pHead;
        while (pNode != null) {
            RandomListNode  pCloned = new RandomListNode(0);
            pCloned.label = pNode.label;
            pCloned.next = pNode.next;
            pCloned.random = null;

            pNode.next = pCloned;//pNode 的下一个节点是PCloned


            pNode = pCloned.next;// pNode和pCloned都是节点的引用,指向相应的节点。
        }
    }

    //第二步 根据旧链表的兄弟结点 初始化新链表的兄弟结点
    //下面 假设初始pNode指向节点A 仅说明节点A 与B及其随机指针指向节点的关系
    public static void connectRandom(RandomListNode pHead) {
        RandomListNode pNode = pHead;
        while (pNode != null) {
            RandomListNode pCloned = pNode.next;//pCloned 指向A’
            /**
             * 若A.random存在
             * 则A'.random也一定存在。则A'的随机(random)节点为A的随机节点的下一个节点
             */
            if (pNode.random != null) {
                pCloned.random = pNode.random.next;
            }
            pNode = pCloned.next;//引用pNode移动到B节点 ,然后从B节点开始一直往后面......
        }
    }

    //第三步 从旧链表拆分得到新的结点
    public static RandomListNode reconnectNode(RandomListNode pHead) {
        RandomListNode pNode = pHead;
        RandomListNode pClonedHead = null;
        RandomListNode pClonedNode = null;

        if (pNode != null) {//此处并非循环,仅执行一次
            pClonedHead = pClonedNode = pNode.next;//引用  pClonedHead 和 pClonedNode指向A'
            pNode.next = pClonedNode.next;//A的下一个节点是B
            pNode = pNode.next;// 引用pNode指向B
        }

        while (pNode != null) {

            // 复制链表的连接
            pClonedNode.next = pNode.next;//A'的下一个节点是B'(也可以说A'指向B’),指向的同时断开A'与B的联系
            pClonedNode = pClonedNode.next;// pClonedNode指向B'

            // 原始链表的连接
            pNode.next = pClonedNode.next;//节点B的下一个节点是C
            pNode = pNode.next;//引用pNode指向C
        }
        return pClonedHead;
    }

    //分为三个步骤
    //第一步 在原链表每个结点后面分别复制创建新的结点
    //第二步 根据旧链表的兄弟结点 初始化新链表的兄弟结点
    //第三步 从旧链表拆分得到新的结点
    public static RandomListNode Clone(RandomListNode pHead) {
         if(pHead==null) return null;//考虑为空的情形
        cloneNodes(pHead);
        connectRandom(pHead);
        return reconnectNode(pHead);
    }
}

其中,分拆链表也可以写为:

但是,前面Clone方法(如果没有 if(pHead==null) return null; 会不通过,显示仅有80%通过。

public static RandomListNode reconnectNode(RandomListNode pHead) {
        RandomListNode pNode = pHead;
        RandomListNode pClonedHead = pHead.next;
        RandomListNode tmp=null;

        while ( pNode.next!=null) {
            tmp = pNode.next;
            pNode.next =tmp.next;
            pNode = tmp;
        }
        return pClonedHead;
    }

 

参考:https://www.cnblogs.com/edisonchou/p/4790090.html  

这个题目比较复杂,但有助于加深对链表的理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值