剑指Offer-27-复杂链表的复制

题目

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

解析

预备知识

考察链表的遍历知识,和对链表中添加节点细节的考察。
同时也考察了对于复杂问题的划分为多个子问题的能力。
该题目所要的描述的链表其实如下:
这里写图片描述
其中实现代表next域,即1->2->3->4为最常见的链表形式。而虚线则代表了特殊指针,可以指向任意节点(包括自身和null)。比如1的特殊指针指向3,2的特殊指针指向1,3的特殊指针指向null,4的特殊指针也指向3。

思路一

我们的目标就是对类似上述的链表进行复制操作。
这个问题看似复杂,如果我们把该问题换分为next子问题(普通指针)和random子问题(特殊指针)。
对于next子问题:我们对于只需遍历一遍原链表并赋值该链表上的每一个节点,把这些复制的节点通过next域连接即可,非常简单。
对于random子问题:我们知道原链表的某一节点的random指向的节点位置必然与对应复制链表中节点的random指向的节点位置相同。简单解释就是,比如原链表的第2个位置的节点的random指向第一个位置节点,那么在复制链表中第2个位置的节点的random必然也指向第一个位置的节点。有了这样的结论,random子问题迎刃而解,我们只需对计算出原链表中每一个节点的random指向的位置即可,这可以从头开始遍历直到与random指针相同,记录期间走的步长。在复制链表中同样走相同的步长,即可确定复制链表中对应节点random的指向的位置。

    /**
     * 复杂链表的复制
     * @param pHead
     * @return
     */
    public static RandomListNode Clone(RandomListNode pHead) {
        //仅看next,来复制整个链表
        RandomListNode copyList = ClonesNodes(pHead);
        //连接random域
        ConnectSiblingNodes(pHead, copyList);
        return copyList;
    }

    /**
     * 考察每一个节点的random在原链表中的位置,从头开始遍历直到random指定的节点为止,计算所需步长
     * 然后在复制的链表中走相应的步长即可达到其节点对应的random域
     * @param pHead
     * @param copyList
     */
    private static void ConnectSiblingNodes(RandomListNode pHead, RandomListNode copyList) {
        RandomListNode p = pHead, q = copyList;
        while(p != null) {
            RandomListNode randomNext = p.random;
            if(randomNext != null) {
                int steps = 0; //所需步长
                RandomListNode t = pHead;
                while(t != randomNext) {
                    t = t.next;
                    steps++;
                }
                t = copyList;
                int count = 0; //记录已走步长
                while(count < steps) {
                    t = t.next;
                    count++;
                }
                q.random = t;
            }
            p = p.next;
            q = q.next;
        }
    }

    /**
     * 仅按next域,复制整个链表
     * 我们对于复制的链表添加了一个头结点,方便添加节点和返回头指针!
     * @param pHead
     * @return
     */
    private static RandomListNode ClonesNodes(RandomListNode pHead) {
        RandomListNode copyList = new RandomListNode(0);
        RandomListNode p = pHead, q = copyList;
        while(p != null) {
            RandomListNode node = new RandomListNode(p.label);
            q.next = node;
            q = q.next;
            p = p.next;
        }
        return copyList.next;
    }
思路二

通过思路一可知,原链表中某一节点的random域的位置必然与其对应复制链表中节点的random域位置一样。在思路一种我们通过遍历的方式确定了random域的位置,复杂度达到了O(n^2)
那么我们如何降低查找random域的位置呢?
这里可以使用hash的思想,我们把键值对

     /**
     * 复杂链表的复制
     * @param pHead
     * @return
     */
    public static RandomListNode Clone2(RandomListNode pHead) {
        //仅看next,来复制整个链表
        RandomListNode copyList = ClonesNodes2(pHead);
        HashMap<RandomListNode, RandomListNode> record = initRecord(pHead, copyList);
        //连接random域
        ConnectSiblingNodes2(pHead, copyList, record);
        return copyList;
    }

    /**
     * 记录原链表与复制链表结点之间的对应关系
     * @param pHead
     * @param copyList
     * @return
     */
    private static HashMap<RandomListNode, RandomListNode> initRecord(RandomListNode pHead, RandomListNode copyList) {
        HashMap<RandomListNode, RandomListNode> record = new HashMap<>();
        RandomListNode p = pHead, q = copyList;
        while(p != null) {
            record.put(p, q);
            p = p.next;
            q = q.next;
        }
        return record;
    }

    /**
     * 通过查record确定random域的位置
     * @param pHead
     * @param copyList
     * @param record
     */
    private static void ConnectSiblingNodes2(RandomListNode pHead, RandomListNode copyList, HashMap<RandomListNode, RandomListNode> record) {
        RandomListNode p = pHead, q = copyList;
        while(p != null) {
            RandomListNode randomNext = p.random;
            q.random = record.get(randomNext);
            p = p.next;
            q = q.next;
        }
    }

    /**
     * 仅按next域,复制整个链表
     * 我们对于复制的链表添加了一个头结点,方便添加节点和返回头指针!
     * @param pHead
     * @return
     */
    private static RandomListNode ClonesNodes2(RandomListNode pHead) {
        RandomListNode copyList = new RandomListNode(0);
        RandomListNode p = pHead, q = copyList;
        while(p != null) {
            RandomListNode node = new RandomListNode(p.label);
            q.next = node;
            q = q.next;
            p = p.next;
        }
        return copyList.next;
    }
思路三

思路三的思想很巧妙,它不是利用哈希来确定random域的位置,而是在利用next域复制原链表的时候,把每一个节点的副本都连接在该节点的后面,这样我们也可以在O(1)的时间内查找到random域位置。比如我们把上图按这样的方式复制链表为:
这里写图片描述
然后我们为复制的每一节点初始化其random域,比如4的random域为3,那么4’的random域为3’,这样可以通过3.next直接得到了3’的位置,同理对其他节点也是这样处理,得到:
这里写图片描述
最后一步就是拆分这个链表得到原链表和复制链表,原链表的节点都处于奇数位置,复制链表的节点都处于偶数位置。拆分如下:
这里写图片描述

    public static RandomListNode Clone3(RandomListNode pHead) {
        ClonesNodes3(pHead);
        ConnectSiblingNodes3(pHead);
        //拆分
        RandomListNode copyList = splitList(pHead);
        return copyList;
    }

    /**
     * 拆分链表
     * @param pHead
     * @return
     */
    private static RandomListNode splitList(RandomListNode pHead) {
        RandomListNode p = pHead, copyList = null;
        if(p != null) {
            copyList = p.next;
            p.next = copyList.next;
            p = p.next;
        }
        RandomListNode q = copyList;
        while(p != null) {
            q.next = p.next;
            q = q.next;
            p.next = q.next;
            p = p.next;
        }
        return copyList;
    }


    private static void ConnectSiblingNodes3(RandomListNode pHead) {
        RandomListNode p = pHead, q;
        while(p != null) {
            q = p.next;
            RandomListNode random = p.random;
            if(random != null) {
                q.random = random.next;
            }
            p = q.next;
        }
    }


    private static void ClonesNodes3(RandomListNode pHead) {
        RandomListNode p = pHead;
        while(p != null) {
            RandomListNode node = new RandomListNode(p.label);
            node.next = p.next;
            p.next = node;
            p = node.next;
        }
    }

总结

还是老话,复杂问题划分为子问题来做。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值