复杂链表的复制(剑指OFFER面试题35)

题目描述

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

public class RandomListNode
{
     public int label;
     public RandomListNode next, random;
     public RandomListNode(int x)
     {
         this.label = x;
     }
 }

思路

这个问题,大家可能第一思路是把过程分为两步:

第一步是复制原始链表上的每个节点,并用next连接起来;
第二步是设置每个节点的random指针。
假设原始链表中的某个节点N的random指向S,由于S在链表的位置可能在N前面也有可能在N的后面,所以要定位S的位置需要从原始链表的头节点开始找。如果从原始链表的头节点沿着next经过s步找到S,那么复制链表上节点N的random离复制链表上头节点的距离也是s步。用这种方法就能复制链表上的每个节点设置random指针了。
对于一个含有N个节点的链表,由于定位每个节点的random都要从链表头节点开始经过O(N)步才能找到,因此这种方法总得时间复杂度是O(N^2)。

由于上述方法的时间主要花费在定位节点random上,所以我们考虑怎么去优化这方面。我们依然分两步:

第一步,依然复制所有原始链表的所有节点,使每个节点N都有对应的节点N’,然后把这些节点用next指针连接起来,同时创建一个哈希表来保存(N,N’)的配对信息;
第二步,设置复制链表上每个节点的random指针。
如果在原始链表中节点N的random指向S,那么在复制链表中,对应的N’应该指向S’ 。由于有了哈希表,所以可以用O(1)的时间根据S找到S’ 。
第二种方法相当于用空间换时间。对于有n个节点的链表,我们需要一个大小为O(N)的哈希表,也就是说,我们以O(N)的空间消耗吧时间复杂度从O(N^2)降到了O(N)。

那么,我们能不能换一种更优的思路呢?在不用辅助空间的情况下实现O(N)的时间效率。

第三种方法,分为三步:
第一步,依然根据原始链表的的每个节点N创建对应的N’,这一次,我们把N‘连接在N的后面;
第二步,设置复制出来的节点的random。假设原来链表上的N的random指向S,那么其对应的复制出来的N‘指向N的random的下一个节点S‘;
第三步,把这个长链表拆分为两个链表:把奇数位置的节点用next连接起来就是原始链表,把偶数位置的节点用next连接起来就是复制的链表。

测试用例大家需要考虑以下两点:

1、功能测试:节点中的random指向节点自身;两个节点的random形成环状结构;链表中只有一个节点。
2、特殊输入测试:头节点为空。

以下为参考代码:
using System;

namespace 复杂链表的复制
{
    class Program
    {
        static void Main(string[] args)
        {
            RandomListNode pHead = CreateRandomList(null);
            Solution s = new Solution();
            s.PrintRandomList(pHead);
            Console.WriteLine();
            s.PrintRandomList(s.Clone(pHead));
        }

        private static RandomListNode CreateRandomList(RandomListNode pHead)
        {
            RandomListNode pNode = pHead;
            if (pNode == null)
                pNode = new RandomListNode(0);
            pHead = pNode;
            for(int i = 1; i < 10; i++)
            {
                RandomListNode temp = new RandomListNode(i);
                pNode.next = temp;
                pNode = temp;
            }
            pNode = pHead;
            for(int i = 0; i < 9; i++)
            {
                if (i == 1)
                {
                    pNode.random = pNode.next.next.next;
                }
                else if (i == 5)
                {
                    pNode.random = pHead.next.next;
                }
                else if (i == 6)
                {
                    pNode.random = pNode.next.next;
                }
                pNode = pNode.next;
            }

            return pHead;
        }
    }

    class Solution
    {
        /// <summary>
        /// 通过三步来实现链表的复制
        /// </summary>
        /// <param name="pHead">链表的头节点</param>
        /// <returns>新链表的头节点</returns>
        public RandomListNode Clone(RandomListNode pHead)
        {
            if (pHead == null)
                return null;

            RandomListNode pNode = pHead;
            pNode = CreateNodes(pNode);
            pNode = ConnectRandomNodes(pNode);
            pNode = DeleteOldNodes(pNode);
            return pNode;
        }

        /// <summary>
        /// 步骤一:复制原始链表的任意节点N并创造新节点N',再把N'链接到N的后面
        /// </summary>
        /// <param name="pHead">链表的头节点</param>
        /// <returns>返回新链表的头节点</returns>
        private RandomListNode CreateNodes(RandomListNode pHead)
        {
            RandomListNode pNode = pHead;

            while (pNode != null)
            {
                //复制当前节点
                RandomListNode temp = new RandomListNode(pNode.label)
                {
                    //把复制的节点放到当前节点后面,并令指针指向下一个节点
                    next = pNode.next
                };
                pNode.next = temp;
                pNode = temp.next;
            }

            return pHead;
        }

        /// <summary>
        /// 第二步,设置复制出来的节点的随机指针
        /// </summary>
        /// <param name="pHead"></param>
        /// <returns></returns>
        private RandomListNode ConnectRandomNodes(RandomListNode pHead)
        {
            RandomListNode pNode = pHead;

            while (pNode != null)
            {
                //为存在随机节点指针的节点的复制节点 赋值
                if (pNode.random != null)
                {
                    pNode.next.random = pNode.random.next;
                }
                pNode = pNode.next.next;
            }

            return pHead;
        }

        /// <summary>
        /// 拆分链表,把复制出来的节点在一个新的链表中输出出来
        /// </summary>
        /// <param name="pHead"></param>
        /// <returns></returns>
        private RandomListNode DeleteOldNodes(RandomListNode pHead)
        {
            RandomListNode pNode = pHead;
            RandomListNode clonedList = null;
            RandomListNode clonedNode = null;
            if (pNode != null)
            {
                clonedList = clonedNode = pNode.next;
                pNode.next = clonedNode.next;
                pNode = pNode.next;
            }
            while (pNode != null)
            {
                clonedNode.next = pNode.next;
                clonedNode = clonedNode.next;
                pNode.next = clonedNode.next;
                pNode = pNode.next;
            }
            return clonedList;
        }

        /// <summary>
        /// 打印链表
        /// </summary>
        /// <param name="pHead">链表的头节点</param>
        public void PrintRandomList(RandomListNode pHead)
        {
            if (pHead == null)
            {
                Console.WriteLine("null");
                return;
            }

            RandomListNode pNode = pHead;
            while (pNode != null)
            {
                if (pNode.next != null && pNode.random != null)
                    Console.WriteLine(pNode.label + "\t" + pNode.next.label + "\t" + pNode.random.label);
                else if (pNode.next == null)
                    Console.WriteLine(pNode.label + "\tnull\tnull");
                else if (pNode.random == null)
                    Console.WriteLine(pNode.label + "\t" + pNode.next.label + "\tnull");

                pNode = pNode.next;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值