问题 使用指向除下一节点之外的随机节点的指针复制LinkedList


问:链表的每个节点都有一个随机指针(除了下一个指针),它可以随机指向另一个节点或为空。你会如何复制这样的链表?

答:这就是我所拥有的,我只是想批准这是否是最佳方式。

由于没有指定空间限制,我将使用a LinkedHashSet 和a LinkedHashMap (我可以想象人们已经在分歧中点头;))

第一次迭代:显而易见 - 从列表中读取每个节点以进行复制并在新列表上创建节点。然后,像这样读取随机节点: this.random.data 并插入 LinkedHashSet

第二次迭代:遍历新列表并将每个节点的数据添加为第一列,将节点本身作为第二列添加到 LinkedHashMap (不一定要链接,但我只是顺其自然)。

第三次迭代:迭代 LinkedHashSet (这就是为什么这需要链接 - 可预测的排序)和新列表同时进行的原因。对于第一个节点,请阅读第一个节点 LinkedHashSet,查找相应的对象 LinkedHashMap 并将随机节点添加到新列表中的当前节点。

3次迭代似乎有点疯狂,但尝试将复杂性保持为O(N)。任何改进O(3N)空间要求和O(3N)运行时复杂性的解决方案都会很棒。谢谢!


编辑:来自的条目 LinkedHashSet 在进入时可以删除 LinkedHashMap,所以这只需要O(2N)空间。


7759
2018-04-01 06:20


起源



答案:


MahlerFive指出我认为你可以用O(2N)运行时复杂度和O(N)空间复杂度来做到这一点。

我们假设你有

public class Node {
    private Node next;
    private Node random;
    private String data;
    // getters and setters omitted for the sake of brevity
}

我会做一个链表的深层复制 Node如下:

private Node deepCopy(Node original) {
    // We use the following map to associate newly created instances 
    // of Node with the instances of Node in the original list
    Map<Node, Node> map = new HashMap<Node, Node>();
    // We scan the original list and for each Node x we create a new 
    // Node y whose data is a copy of x's data, then we store the 
    // couple (x,y) in map using x as a key. Note that during this 
    // scan we set y.next and y.random to null: we'll fix them in 
    // the next scan
    Node x = original;
    while (x != null) {
        Node y = new Node();
        y.setData(new String(x.getData()));
        y.setNext(null);
        y.setRandom(null);
        map.put(x, y);
        x = x.getNext();
    }
    // Now for each Node x in the original list we have a copy y 
    // stored in our map. We scan again the original list and 
    // we set the pointers buildings the new list
    x = original;
    while (x != null) {
            // we get the node y corresponding to x from the map
        Node y = map.get(x);
            // let x' = x.next; y' = map.get(x') is the new node 
            // corresponding to x'; so we can set y.next = y'
        y.setNext(map.get(x.getNext()));
            // let x'' = x.random; y'' = map.get(x'') is the new 
            // node corresponding to x''; so we can set y.random = y''
        y.setRandom(map.get(x.getRandom()));
        x = x.getNext();
    }
    // finally we return the head of the new list, that is the Node y
    // in the map corresponding to the Node original
    return map.get(original);
}

编辑:我发现这个问题与提出的问题重复 这里:你找到了 回答 这显示了如何在没有额外空间的O(3N)运行时复杂性中解决这个问题:非常巧妙!但它使用C指针的技巧,我不知道如何在java中做同样的事情。


11
2018-04-01 08:34



看起来很棒 - 谢谢......我很高兴我问过这个问题。我只是在这里挑剔,但使用的空间是O(2N)。还要感谢其他链接。 - user183037
您可以通过添加更多评论来详细说明,因为我正在努力理解并且很难获得它。 - Rachel
@Rachel:我在代码中添加了注释:希望能帮助您理解它。 - MarcoS
能帮我理解这个问题吗?我不确定为什么这个问题如此受欢迎并在许多采访中被问到。我们不是只是简单地创建一个新节点并简单地从原始节点复制所有数据?我的意思是节点和指针的数据。为什么我们需要分两步完成?提前致谢! - AKS
@AKS:想象一下,在原始列表中,您有一个节点X,指向下一个节点X + 1的指针,以及指向随机节点X + n的指针。在新列表中创建新节点时,无法设置其指针,因为尚未创建X + 1和X + n的副本。我希望这澄清:) - MarcoS


答案:


MahlerFive指出我认为你可以用O(2N)运行时复杂度和O(N)空间复杂度来做到这一点。

我们假设你有

public class Node {
    private Node next;
    private Node random;
    private String data;
    // getters and setters omitted for the sake of brevity
}

我会做一个链表的深层复制 Node如下:

private Node deepCopy(Node original) {
    // We use the following map to associate newly created instances 
    // of Node with the instances of Node in the original list
    Map<Node, Node> map = new HashMap<Node, Node>();
    // We scan the original list and for each Node x we create a new 
    // Node y whose data is a copy of x's data, then we store the 
    // couple (x,y) in map using x as a key. Note that during this 
    // scan we set y.next and y.random to null: we'll fix them in 
    // the next scan
    Node x = original;
    while (x != null) {
        Node y = new Node();
        y.setData(new String(x.getData()));
        y.setNext(null);
        y.setRandom(null);
        map.put(x, y);
        x = x.getNext();
    }
    // Now for each Node x in the original list we have a copy y 
    // stored in our map. We scan again the original list and 
    // we set the pointers buildings the new list
    x = original;
    while (x != null) {
            // we get the node y corresponding to x from the map
        Node y = map.get(x);
            // let x' = x.next; y' = map.get(x') is the new node 
            // corresponding to x'; so we can set y.next = y'
        y.setNext(map.get(x.getNext()));
            // let x'' = x.random; y'' = map.get(x'') is the new 
            // node corresponding to x''; so we can set y.random = y''
        y.setRandom(map.get(x.getRandom()));
        x = x.getNext();
    }
    // finally we return the head of the new list, that is the Node y
    // in the map corresponding to the Node original
    return map.get(original);
}

编辑:我发现这个问题与提出的问题重复 这里:你找到了 回答 这显示了如何在没有额外空间的O(3N)运行时复杂性中解决这个问题:非常巧妙!但它使用C指针的技巧,我不知道如何在java中做同样的事情。


11
2018-04-01 08:34



看起来很棒 - 谢谢......我很高兴我问过这个问题。我只是在这里挑剔,但使用的空间是O(2N)。还要感谢其他链接。 - user183037
您可以通过添加更多评论来详细说明,因为我正在努力理解并且很难获得它。 - Rachel
@Rachel:我在代码中添加了注释:希望能帮助您理解它。 - MarcoS
能帮我理解这个问题吗?我不确定为什么这个问题如此受欢迎并在许多采访中被问到。我们不是只是简单地创建一个新节点并简单地从原始节点复制所有数据?我的意思是节点和指针的数据。为什么我们需要分两步完成?提前致谢! - AKS
@AKS:想象一下,在原始列表中,您有一个节点X,指向下一个节点X + 1的指针,以及指向随机节点X + n的指针。在新列表中创建新节点时,无法设置其指针,因为尚未创建X + 1和X + n的副本。我希望这澄清:) - MarcoS


您可以使用2N步骤和包含N个元素的地图执行此操作。

  1. 按照“下一步”指针走旧列表。对于您访问的每个节点,将节点添加到新列表,将新列表中的上一个节点连接到新节点,将旧节点随机指针存储在新新节点中,然后存储旧节点指针到地图中的新节点指针。

  2. 遍历新列表,并为每个随机指针在地图中查找以查找新列表中的关联节点以替换它。


4
2018-04-01 06:37



“然后将旧节点指针的映射存储到地图中的新节点指针” - 您如何在同一次迭代中执行此操作?甚至可能尚未创建此随机指针的新节点。例如,您在节点1上,随机指针指向节点5.您尚未创建节点5,因此无法将其添加到节点5 HashMap 然而。或者你的意思完全是什么? - user183037
“新节点”是指与旧节点关联的节点,而不是随机指针指向的节点。例如。将旧列表的第一个节点映射到新列表的第一个节点,将旧列表的第二个节点映射到新列表的第二个节点,等等。 - MahlerFive
@MarcoS'代码帮助澄清了您的观点 - 谢谢。 - user183037


我最近也在采访中被问过这个问题。 这是我提出的建议。 创建原始列表节点的映射,其中每个节点的addreess将是键,随机指针的偏移量将是值。 现在使用原始地图中的random pointer = null创建一个新的链表。 最后,迭代原始列表,借助map获取原始指针的偏移量,并使用该偏移量链接新创建的地图中的随机指针。

面试官最后并不开心。可能正在寻找一些更好的方法,或者他心中有了固定的答案,无法掌握解决问题的新方法。


1
2018-06-24 07:39





在O(n)时间和恒定的空间

public class CloneLinkedListWithRandomPointer {

public static void main(String[] args) throws Exception {
    SpecialLink link = new SpecialLink(1);
    SpecialLink two = new SpecialLink(2);
    SpecialLink three = new SpecialLink(3);
    SpecialLink four = new SpecialLink(4);
    SpecialLink five = new SpecialLink(5);

    link.next = two;
    two.next = three;
    three.next = four;
    four.next = five;

    link.random = four;
    two.random = five;
    three.random = null;
    four.random = five;
    five.random=link;

    SpecialLink copy = cloneSpecialLinkedList(link);

    System.out.println(link);
    System.out.println(copy);
}


public static SpecialLink cloneSpecialLinkedList(SpecialLink link) throws Exception{

    SpecialLink temp = link;
    while(temp != null){
        temp.next = (SpecialLink) temp.clone();
        temp = temp.next==null?temp.next:temp.next.next;
    }

    temp = link;
    while(temp != null){
        temp.next.random = temp.random!=null?temp.random.next:null;
        temp = temp.next==null?temp.next:temp.next.next;
    }


    SpecialLink copy = link.next;

    temp = link;
    SpecialLink copyTemp = copy;

    while(temp.next!= null && copyTemp.next != null){
        temp.next = temp.next.next;
        copyTemp.next = copyTemp.next.next;

        temp = temp.next;
        copyTemp = copyTemp.next;

    }

    return copy;
}

}

class SpecialLink implements Cloneable{

enum Type{
    ORIGINAL,COPY
}

int val;
SpecialLink next;
SpecialLink random;
Type type;

public void setValue(int value){
    this.val = value;
}

public SpecialLink addNode(int value){
    return next = new SpecialLink(value);
}

public SpecialLink(int value) {
    super();
    this.val = value;
    this.type = Type.ORIGINAL;
}

@Override
public String toString() {
    SpecialLink temp = this;
    StringBuilder builder = new StringBuilder();
    while(temp != null){
        builder.append(temp.val).append("--").append(temp.type.toString()).append("->").append(temp.random == null? null:temp.random.val).append("--").append(temp.random == null? null:temp.random.type);
        builder.append(", ");
        temp = temp.next;
    }
    return builder.toString();
}

@Override
public Object clone() throws CloneNotSupportedException {
    // TODO Auto-generated method stub
    SpecialLink clone = (SpecialLink) super.clone();
    clone.type = Type.COPY;
    return clone;
}
}

0
2017-10-03 18:52





走吧 list 并使用 clone()


0
2018-04-01 06:26



这是一个面试问题 - 他们不允许你使用可用的方法。 - user183037
我认为他是经过深刻的复制。 - aioobe
@aioobe - 你是对的,这是另一个问题。 - user183037


我为@MahlerFive的解决方案编写了代码,它没有映射。

这是代码:

private static class Node {
    private String item;
    private Node next;
    private Node random;
}

public static Node cloneLinkedStructure(Node head) {
    // make holes after each original node
    for (Node p = head; p != null;) {
        Node pnext = p.next;
        Node hole = new Node();
        hole.item = ".";
        p.next = hole;
        hole.next = pnext;
        p = pnext;
    }

    Node fakeHead = new Node(); // fake new head
    Node q = fakeHead;
    Node p = head;
    while (p != null) {
        // build the new linked structure
        Node oldq = q;
        q = new Node();
        q.item = p.item;
        oldq.next = q;
        q.random = p.random.next; // link to a hole

        Node hole = p.next;
        hole.random = q; // use link RANDOM as a backward link to new node

        p = hole.next;
    }
    q.next = null;

    Node newHead = fakeHead.next; // throw fake head
    // build random links for the new linked structure
    for (q = newHead; q != null; q = q.next)
        q.random = q.random.random;

    // delete holes to restore original linked structure
    for (p = head; p != null; p = p.next)
        p.next = p.next.next;

    return newHead;
}

0
2018-02-12 07:19





1)创建节点1的副本并将其插入原始链表中的节点1和节点2之间,创建2的副本并将其插入2和3之间。以这种方式继续,在第N个节点后添加N的副本

2)现在以这种方式复制任意链接

original->next->arbitrary = original->arbitrary->next;  /*TRAVERSE TWO NODES*/

这是有效的,因为原始 - >接下来只是原始副本和原始 - >任意 - >接下来只是任意副本。    3)现在以这种方式在单个循环中恢复原始和复制链接列表。

original->next = original->next->next;
copy->next = copy->next->next;

4)确保original-> next的最后一个元素为NULL。

时间复杂度:O(n)

辅助空间:O(1)

资源


0
2018-03-12 21:55





这是Java实现:

public static <T> RandomLinearNode<T> clone(RandomLinearNode<T> head) {
    if (head == null) {
        return head;
    }
    RandomLinearNode<T> itr = head, temp;

    // insert copy nodes after each original nodes
    while (itr != null) {
        temp = new RandomLinearNode<T>(itr.getElement());
        temp.next(itr.next());
        itr.next(temp);
        itr = temp.next();
    }
    // copy the random pointer
    itr = head;
    while (itr != null && itr.next() != null) {
        if (itr.random() != null) {
            itr.next().random(itr.random().next());
        }
        itr = itr.next().next();
    }
    // break the list into two
    RandomLinearNode<T> newHead = head.next();
    itr = head;
    while (itr != null && itr.next() != null) {
        temp = itr.next();
        itr.next(temp.next());          
        itr = temp.next();
    }
    return newHead;
}

这是单元测试

@Test
public void cloneLinkeListWithRandomPointerTest() {
    RandomLinearNode<Integer> one = new RandomLinearNode<Integer>(1, null, null);
    RandomLinearNode<Integer> two = new RandomLinearNode<Integer>(2, one, null);
    RandomLinearNode<Integer> three = new RandomLinearNode<Integer>(3, two, null);
    RandomLinearNode<Integer> four = new RandomLinearNode<Integer>(4, three, null);
    RandomLinearNode<Integer> five = new RandomLinearNode<Integer>(5, four, four);
    RandomLinearNode<Integer> six = new RandomLinearNode<Integer>(6, five, two);
    RandomLinearNode<Integer> seven = new RandomLinearNode<Integer>(7, six, three);
    RandomLinearNode<Integer> eight = new RandomLinearNode<Integer>(8, seven, one);

    RandomLinearNode<Integer> newHead = LinkedListUtil.clone(eight);
    assertThat(eight, not(sameInstance(newHead)));
    assertThat(newHead.getElement(), equalTo(eight.getElement()));
    assertThat(newHead.random().getElement(), equalTo(eight.random().getElement()));

    assertThat(newHead.next().getElement(), equalTo(eight.next().getElement()));
    assertThat(newHead.next().random().getElement(), equalTo(eight.next().random().getElement()));

    assertThat(newHead.next().next().getElement(), equalTo(eight.next().next().getElement()));
    assertThat(newHead.next().next().random().getElement(), equalTo(eight.next().next().random().getElement()));


    assertThat(newHead.next().next().next().getElement(), equalTo(eight.next().next().next().getElement()));
    assertThat(newHead.next().next().next().random().getElement(), equalTo(eight.next().next().next().random().getElement()));
}

0
2018-04-09 13:34