Java 使用指向下一个节点之外的随机节点的指针复制LinkedList

Java 使用指向下一个节点之外的随机节点的指针复制LinkedList,java,algorithm,linked-list,Java,Algorithm,Linked List,Q:链表的每个节点都有一个随机指针(除了下一个指针之外),它可以随机指向另一个节点或为空。你将如何复制这样一个linkedlist 答:这是我的资料,我只是想确认这是否是最好的方式 由于没有指定空间限制,我将使用LinkedHashSet和LinkedHashMap(我可以想象人们已经不同意地点头;) 第一次迭代:执行显而易见的操作—从要复制的列表中读取每个节点,并在新列表中创建节点。然后,像这样读取随机节点:this.random.data并插入LinkedHashSet 第二次迭代:迭代新列

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

答:这是我的资料,我只是想确认这是否是最好的方式

由于没有指定空间限制,我将使用
LinkedHashSet
LinkedHashMap
(我可以想象人们已经不同意地点头;)

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

第二次迭代:迭代新列表,将每个节点的数据作为第一列,将节点本身作为第二列添加到
LinkedHashMap
(不必链接,但我只是按照流程进行)

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

3次迭代看起来确实有点疯狂,但尝试将复杂性保持为O(N)。任何改善O(3N)空间需求和O(3N)运行时复杂性的解决方案都是非常好的。谢谢



编辑:在
LinkedHashMap
中创建条目时,可以删除
LinkedHashSet
中的条目,因此这只需要O(2N)个空间。

遍历
列表并使用
克隆()

您可以通过2N个步骤和一个包含N个元素的映射来完成此操作


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

  • 遍历新列表,对于每个随机指针,在映射中查找它,以在新列表中找到要替换的关联节点

  • 我认为可以用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
    }
    
    我会做一个
    节点
    链接列表的深度拷贝,如下所示:

    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);
    }
    
    private Node deepCopy(节点原始){
    //我们使用以下映射来关联新创建的实例
    //具有原始列表中节点实例的节点的
    Map Map=newhashmap();
    //我们扫描原始列表,并为每个节点x创建一个新的
    //节点y的数据是x的数据的副本,然后我们存储
    //使用x作为键在地图中耦合(x,y)。请注意,在此过程中
    //扫描我们将y.next和y.random设置为null:我们将在
    //下一次扫描
    节点x=原始节点;
    while(x!=null){
    节点y=新节点();
    y、 setData(新字符串(x.getData());
    y、 setNext(空);
    y、 setRandom(空);
    map.put(x,y);
    x=x.getNext();
    }
    //现在,对于原始列表中的每个节点x,我们都有一个副本y
    //存储在地图中。我们再次扫描原始列表并
    //我们在新列表中设置了指针
    x=原件;
    while(x!=null){
    //我们从映射中得到对应于x的节点y
    节点y=map.get(x);
    //设x'=x.next;y'=map.get(x')为新节点
    //对应于x';因此我们可以设置y.next=y'
    y、 setNext(map.get(x.getNext());
    //设x'=x.random;y'=map.get(x'')是新的
    //节点对应于x“”;因此我们可以设置y.random=y“”
    y、 setRandom(map.get(x.getRandom());
    x=x.getNext();
    }
    //最后,我们返回新列表的头,即节点y
    //在与原始节点对应的贴图中
    返回地图。获取(原始);
    }
    

    编辑:我发现这个问题与所问的问题是重复的:在这里,你可以找到一个演示如何在没有额外空间的情况下以O(3N)运行时复杂性解决这个问题的方法:非常巧妙!但是它在C指针上使用了一个技巧,我不知道在java中如何做到这一点。

    在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;
    }
    }
    

    我最近在采访中也被问到这个问题。 以下是我的建议。 创建原始列表节点的映射,其中每个节点的addrees将是键,随机指针的偏移量将是值。 现在,从原始映射创建一个随机指针=null的新链表。 最后,在map的帮助下遍历原始列表,获取原始指针的偏移量,并使用该偏移量链接新创建的map中的随机指针


    面试官最后不高兴。可能是在寻找更好的方法,或者他脑子里有固定的答案,无法掌握解决问题的新方法。

    我为@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;
    }
    
    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的最后一个元素为空

    时间复杂度:O(n)

    辅助空间:O(1)


    以下是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()));
    }