剑指Offer:复杂链表的复制

请实现函数复制一个复杂链表。在复杂链表中,每个结点除了有一个next引用“指向”下一个结点外,还有一个sibling引用“指向”链表中的任意结点或者null。
复杂链表的图示:
这里写图片描述

O(n^2)的解法,无法拿下offer

第一反应的解决思路是两步走战略

  • 第一步:先不管sibling引用的问题,先把整个链表中的每一个结点都复制并用next连接起来,这一步还是比较简单的;
  • 第二步:每个结点都复制完后就该设置每个结点的sibling了。如果链表中有一个结点N,它对应复制后的结点为N’,N结点的sibling指向结点S,S对应复制后的结点为S’,现在我们要设置N’的sibling,我们通过N结点很容易找到S,但是不能找到S’,所有我们从头开始遍历走k步后找到S结点,那么很容易知道S’结点也是从复制后的链表的头结点走k步。这样我们就找到了S’并能设置好N’的sibling。

对于n个结点的链表,每设置一个结点的sibling就要从头开始经过O(n)步才能找到,所以时间复杂度为O(n^2)。


空间换取时间,O(n)的解法

分析上面那种方法我们会发现大部分的时间都花费在了设置sibling的地方,所以我们想办法简化寻找S’的时间。
我们使用哈希表将<N, N'>这一配对信息放入表中,有了哈希表我们就可以在O(1)的时间根据S找到S’。
这种方法就是典型的空间换时间的方法,通过O(n)的空间将时间复杂度降到了O(n)。


不使用辅助空间,O(n)的解法

上面一种方法中我们使用了辅助空间建立哈希表,我们再换一种思路在不使用辅助空间的情况下实现时间复杂度为O(n)的复制。
我们要将两步走战略升级成三步走战略

  • 第一步:复制每一个结点,并且将这个结点直接插入到原结点后面;
    这里写图片描述
  • 第二步:设置sibling,一句代码搞定:nodeA.next.sibling = nodeA.sibling.next;
    这里写图片描述
  • 第三步:将原结点和复制后的结点分离,复制链表完成。

复杂链表复杂测试环境代码
这里写图片描述
第一步代码实现:

//第一步:复制每一个结点,并且将这个结点直接插入到原结点后面
private static void CloneInsertNode(ComplexList list){
    ComplexNode current = list.head;
    while(current!=null){
        ComplexNode cloned = new ComplexNode();
        cloned.data = current.data;
        cloned.next = current.next;
        current.next = cloned;
        current = cloned.next;
    }
}

这里写图片描述

第二步代码实现:

//第二步:设置sibling
private static void SetSibling(ComplexList list){
    ComplexNode current = list.head;
    while(current!=null){
        ComplexNode cloned = current.next;
        if(current.sibling!=null)
            cloned.sibling = current.sibling.next;
        current = cloned.next;
    }
}

这里写图片描述

第三步代码实现:

//第三步:分离结点
private static ComplexList SeparateList(ComplexList list){
    ComplexNode current = list.head;
    ComplexNode newHead = current.next;
    ComplexList newList = new ComplexList();
    newList.head = newHead;
    while(current!=null){
        ComplexNode cloned = current.next;
        current.next = cloned.next;
        current = cloned.next;
        if(current==null)
            break;
        cloned.next = cloned.next.next;
    }
    return newList;
}

综合测试:

public static void main(String[] args) {
    ComplexList list = new ComplexList();
    //创建链表
    list.insertHead(5);
    list.insertHead(4);
    list.insertHead(3);
    list.insertHead(2);
    list.insertHead(1);
    //设置sibling
    list.getNode(1).sibling = list.getNode(5);//1-->5
    list.getNode(2).sibling = list.getNode(4);//2-->4
    list.getNode(4).sibling = list.getNode(3);//4-->3
    list.getNode(3).sibling = list.getNode(3);//3-->3
    list.getNode(5).sibling = list.getNode(1);//5-->1
    System.out.print("结点1的sibling:");
    list.getNode(1).sibling.displayNode();
    System.out.print("\n结点2的sibling:");
    list.getNode(2).sibling.displayNode();
    System.out.print("\n结点4的sibling:");
    list.getNode(4).sibling.displayNode();
    System.out.print("\n结点3的sibling:");
    list.getNode(3).sibling.displayNode();
    System.out.print("\n结点5的sibling:");
    list.getNode(5).sibling.displayNode();
    System.out.print("\nlist:");
    list.displayList();
    System.out.println("\n================================");

    //创建好复杂链表
    ComplexList list2 = CopyComplexList(list);
    System.out.print("结点1复制后的sibling:");
    list2.getNode(1).sibling.displayNode();
    System.out.print("\n结点2复制后的sibling:");
    list2.getNode(2).sibling.displayNode();
    System.out.print("\n结点4复制后的sibling:");
    list2.getNode(4).sibling.displayNode();
    System.out.print("\n结点3复制后的sibling:");
    list2.getNode(3).sibling.displayNode();
    System.out.print("\n结点5复制后的sibling:");
    list2.getNode(5).sibling.displayNode();
    System.out.print("\nlist2:");
    list2.displayList();
}

private static ComplexList CopyComplexList(ComplexList list){
    if(list==null||list.head==null){
        System.out.println("参数不合法!");
        return null;
    }
    CloneInsertNode(list);
    SetSibling(list);
    return SeparateList(list);
}

这里写图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值