题目
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的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;
}
}
总结
还是老话,复杂问题划分为子问题来做。