《剑指offer》面试题26 复杂链表的复制 Java版
(定义一个新的数据结构,每个节点除了具有普通链表的next域外,还有一个额外的引用指向任意节点。我们要对由该特殊数据结构形成的链表进行复制。)
我的方法:也就是克隆一个这种特殊链表,很快想到先不考虑原链表sibling域,复制出一个新的链表,然后再去给sibling域赋值。由于sibling可以指向任何节点,而且我们是根据原链表的sibling来确定新链表中的sibling,所以每次我们寻找新链表中某个节点的sibling,都要两个指针重新从头开始扫描以确定新链表中的sibling。所以时间复杂度是O(n)+O(n^2)。
public ComplexListNode complexClone(ComplexListNode head){
if(head == null)return null;
ComplexListNode headMark = head;
ComplexListNode newHeadMark = new ComplexListNode(head.val);
ComplexListNode newHead = newHeadMark;
head = head.next;
//仅形成next域
for(; head != null; head = head.next){
newHead.next = new ComplexListNode(head.val);
newHead = newHead.next;
}
//形成sibling域
head = headMark;
newHead = newHeadMark;
for(; head!=null; head=head.next, newHead=newHead.next){
ComplexListNode index = headMark;
ComplexListNode newIndex = newHeadMark;
for(; index!=null; index=index.next,newIndex=newIndex.next){
if(head.sibling == index){
newHead.sibling = newIndex;
}
}
}
return newHeadMark;
}
书中方法一:一看书,果然上面的方法是最烂的,主要时间复杂度集中在第二步确定sibling上面,我们是否能够让每次寻找的时间复杂度减小到O(1)?一般来说牺牲空间复杂度可以降低时间复杂度+一次寻找的时间复杂度是O(1),我们就想到了HashMap。如何使用呢?我们在创建新链表的时候存储原链表每个节点对应的新链表节点,如(old,new),这样在第二步连接sibling的时候就可以根据原链表节点一步找到新链表节点。
public ComplexListNode complexClone2(ComplexListNode head){
if(head == null)return null;
Map<ComplexListNode, ComplexListNode> map = new HashMap<>();
ComplexListNode headMark = head;
ComplexListNode newHeadMark = new ComplexListNode(head.val);
ComplexListNode newHead = newHeadMark;
map.put(headMark, newHead);
head = head.next;
for(; head!=null; head = head.next){
newHead.next = new ComplexListNode(head.val);
map.put(head, newHead.next);
newHead = newHead.next;
}
for(ComplexListNode index = headMark,newIndex = newHeadMark;
index!=null; index=index.next, newIndex=newIndex.next){
newIndex.sibling = map.get(index.sibling);
}
return newHeadMark;
}
书中方法二:书上还讲了一种时间复杂度O(n)空间复杂度O(1)的方法。我们既可以根据原链表的节点迅速确定新链表的节点(即和HashMap一样也存在一种一一对应的关系),又不用额外创建空间。创建新链表时,把新链表的对应节点放在原链表节点的后面可以达到一一对应的效果,最后我们把一整条链表拆开,这样也不会破坏源链表结构,也得到了新链表。从本质上来讲,如果我们完全抛弃原链表的结构去寻找sibling,相当于丢弃了一部分信息,也就是把所有节点当成一个set去遍历寻找。如果考虑了sibling的结构(即把新的节点创建在原节点之后),相当于走了捷径。
public ComplexListNode complexClone3(ComplexListNode head){
copyAndConstruct(head);
linkSibling(head);
return unpackage(head);
}
private void copyAndConstruct(ComplexListNode head){
ComplexListNode index = head;
while(index != null){
ComplexListNode temp = new ComplexListNode(index.val);
temp.next = index.next;
index.next = temp;
index = index.next.next;
}
}
private void linkSibling(ComplexListNode head){
ComplexListNode index = head;
while(index != null){
if(index.sibling == null){
index.next.sibling = null;
}else{
index.next.sibling = index.sibling.next;
}
index = index.next.next;
}
}
private ComplexListNode unpackage(ComplexListNode head){
if(head == null)return null;
ComplexListNode newIndex = head.next;
ComplexListNode newHead = newIndex;
ComplexListNode index = head;
while(index != null){
index.next = newIndex.next;
if(newIndex.next != null){
newIndex.next = newIndex.next.next;
}
index = index.next;
newIndex = newIndex.next;
}
return newHead;
}