题目描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的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;
}
}
}
}