Code Ganker: Copy List with Random Pointer -- LeetCode

2014年3月28日星期五

Copy List with Random Pointer -- LeetCode

原题链接: http://oj.leetcode.com/problems/copy-list-with-random-pointer/
这是一道链表操作的题目,要求复制一个链表,不过链表的每个结点带有一个随机指针,指向某一个结点。
我们先介绍一种比较直接的算法,思路是先按照复制一个正常链表的方式复制,复制的时候把复制的结点做一个HashMap,以旧结点为key,新节点为value。这么做的目的是为了第二遍扫描的时候我们按照这个哈希表把结点的随机指针接上。这个算法是比较容易想到的,总共要进行两次扫描,所以时间复杂度是O(2*n)=O(n)。空间上需要一个哈希表来做结点的映射,所以空间复杂度也是O(n)。代码如下:
public RandomListNode copyRandomList(RandomListNode head) {
    if(head == null)
        return head;
    HashMap<RandomListNode,RandomListNode> map = new HashMap<RandomListNode,RandomListNode>();
    RandomListNode newHead = new RandomListNode(head.label);
    map.put(head,newHead);
    RandomListNode pre = newHead;
    RandomListNode node = head.next;
    while(node!=null)
    {
        RandomListNode newNode = new RandomListNode(node.label);
        map.put(node,newNode);
        pre.next = newNode;
        pre = newNode;
        node = node.next;
    }
    node = head;
    RandomListNode copyNode = newHead;
    while(node!=null)
    {
        copyNode.random = map.get(node.random);
        copyNode = copyNode.next;
        node = node.next;
    }
    return newHead;
}
那么有没有办法可以不用额外空间来完成这个任务呢?还是有的,前面我们需要一个哈希表的原因是当我们访问一个结点时可能它的随机指针指向的结点还没有访问过,结点还没有创建,所以我们需要线性的额外空间。想避免使用额外空间,我们只能通过利用链表原来的数据结构来存储结点。基本思路是这样的,对链表进行三次扫描,第一次扫描对每个结点进行复制,然后把复制出来的新节点接在原结点的next,也就是让链表变成一个重复链表,就是新旧更替;第二次扫描中我们把旧结点的随机指针赋给新节点的随机指针,因为新结点都跟在旧结点的下一个,所以赋值比较简单,就是node.next.random = node.random.next,其中node.next就是新结点,因为第一次扫描我们就是把新结点接在旧结点后面。现在我们把结点的随机指针都接好了,最后一次扫描我们把链表拆成两个,第一个还原原链表,而第二个就是我们要求的复制链表。因为现在链表是旧新更替,只要把每隔两个结点分别相连,对链表进行分割即可。这个方法总共进行三次线性扫描,所以时间复杂度是O(n)。而这里并不需要额外空间,所以空间复杂度是O(1)。比起上面的方法,这里多做一次线性扫描,但是不需要额外空间,还是比较值的。实现的代码如下:
public RandomListNode copyRandomList(RandomListNode head) {
    if(head == null)
        return head;
    RandomListNode node = head;
    while(node!=null)
    {
        RandomListNode newNode = new RandomListNode(node.label);
        newNode.next = node.next;
        node.next = newNode;
        node = newNode.next;
    }
    node = head;
    while(node!=null)
    {
        if(node.random != null)
            node.next.random = node.random.next;
        node = node.next.next;
    }
    RandomListNode newHead = head.next;
    node = head;
    while(node != null)
    {
        RandomListNode newNode = node.next;
        node.next = newNode.next;
        if(newNode.next!=null)
            newNode.next = newNode.next.next;
        node = node.next;
    }
    return newHead;
}
上面介绍了两种方法来解决这个问题,第二种方法利用了原来的链表省去了额外空间,虽然多进行一次扫描,不过对时间复杂度量级没有影响,还是对算法有提高的。这个题目算是比较有难度的链表题目,既有基本操作,也需要一些算法思想。

7 条评论:

  1. public RandomListNode copyRandomList(RandomListNode head) {
    if (head == null)
    return null;
    //复制新点插入相应旧点之后
    RandomListNode r1 = head;
    RandomListNode next =null;
    while(r1!=null){
    next = r1.next;
    RandomListNode copy = new RandomListNode(r1.label);
    r1.next=copy;
    copy.next=next;
    r1=next;
    }

    //复制random pointer
    r1=head;
    while(r1!=null){
    if(r1.random!=null){
    r1.next.random=r1.random.next;
    }
    r1=r1.next.next;
    }

    //删除旧点。分解至两个独立列表
    r1=head;
    RandomListNode newHead = r1.next;
    RandomListNode r2 = newHead;
    while(r2.next!=null){
    r1.next = r2.next;
    r1=r1.next;
    r2.next = r1.next;
    r2=r2.next;
    }
    r1.next=null;
    return newHead;
    } // end
    Hi, 你好。我的代码和你的第二种解法是相同的思路。 但是有Time Limit Exceeded错误,我反复看了好几遍 也没找到哪里写错了,能麻烦楼主帮忙看一下么,非常感谢。

    回复删除
    回复
    1. 排好版的代码 以发上来 还是格式乱了,抱歉

      删除
    2. 经过测试,应该是
      //复制新点插入相应旧点之后
      RandomListNode r1 = head;
      RandomListNode next =null;
      while(r1!=null){
      next = r1.next;
      RandomListNode copy = new RandomListNode(r1.label);
      r1.next=copy;
      copy.next=next;
      r1=next;
      } 这部代码错了,但是我还是没看出来哪里不对

      删除
    3. 我用你的代码跑是accepted哈~ 是不是网络出问题了。。

      删除
  2. 谢谢!下午提交几遍都不过,反反复复核查代码好几遍。晚上再交就过了,不知道leetcode还可以这样@@

    回复删除
  3. 为什么第二种方法空间复杂度不是O(n)?旧节点删除前空间加倍了。

    回复删除
  4. 额外空间看的是除开结果所用的多余的空间,这里节点最后是用在结果链表上,所以没有额外空间哈~ 复杂度是O(1)。

    回复删除