含有随机指针的链表的拷贝(复杂链表的拷贝)

含有随机指针的链表的拷贝(复杂链表的拷贝)

  • 问题引入
  • 方法一 : 使用HashMap保存
  • 方法二 : 方法二的另一种写法
  • 方法三 : 方法二的递归版本
  • 方法四 : O(1)的空间复杂度
  • 完整测试代码

问题引入

给出一个比普通链表多了一个random域的链表结构,要拷贝一份和这个链表相同的链表,random指针域可以指向链表的任意一个结点,包括null,链表的结构如下。

 private static class Node{
        public int value;
        public Node next;
        public Node random;

        public Node(int value) {
            this.value = value;
        }
    }

这个题目在剑指Offer中也出现过。


方法一 : 使用HashMap保存

这个方法很简单,使用一个Map结构,先遍历一遍链表,保存一份每个结点的拷贝,然后再遍历一遍,分别在找到拷贝结点的next域和random域,即找到原先cur的next和random的拷贝(从Map中获取)


    //普通的使用一个HashMap的额外空间为O(n)的方法
    public static Node copyListWithRandom1(Node head){
        if(head == null)return null;
        HashMap<Node,Node>map = new HashMap<>();
        Node cur = head;
        while(cur != null){
            map.put(cur,new Node(cur.value)); // 将当前的节点和这个结点的拷贝结点 put进入map
            cur = cur.next;
        }
        cur = head;
        while(cur != null){
            map.get(cur).next = map.get(cur.next); //设置拷贝节点的next域 为 之前的next域的拷贝
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return map.get(head); //返回拷贝链表的头结点
    }

方法二 : 方法二的另一种写法

方法一的写法是第一次存储每个结点的时候没有直接找到拷贝结点的next域结点,这个方法是在拷贝原结点的时候,顺便拷贝了结点的next域,拷贝完next域之后,最后就只要拷贝random域了。

    //第一种方法其他的写法
    public static Node copyListWithRandom11(Node head){
        if(head == null)return null;
        HashMap<Node,Node>map = new HashMap<>();

        Node copyHead = new Node(head.value);
        map.put(head,copyHead);
        Node q = copyHead,p = head.next;
        while(p != null){  //这个和上面不同的是 顺便一开始就找到了next域
            q.next = new Node(p.value);
            map.put(p,q.next);
            p = p.next;
            q = q.next;
        }
        q.next = null; //将拷贝链表的最后一个结点的next域为null
        //找到拷贝链表的random指针

        p = head; q = copyHead;
        while(p != null){
            q.random = map.get(p.random);
            p = p.next;
            q = q.next;
        }
        return copyHead;
    }

方法三 : 方法二的递归版本

上面的方法也可以把拷贝的过程写成一个递归函数


    //上面的方法也可以写成递归的
    public static Node copyListWithRandom111(Node head){
        if(head == null) return null;
        HashMap<Node,Node>map = new HashMap<>();
        Node copyHead = makeMp(head,map);

        Node p = head,q = copyHead;
        while(p != null){
            q.random = map.get(p.random);
            p = p.next;
            q = q.next;
        }

        return copyHead;
    }
    private static Node makeMp(Node head, HashMap<Node, Node> map) {
        if(head == null)return null;
        Node copy = new Node(head.value);
        map.put(head,copy);
        copy.next = makeMp(head.next,map);
        return copy;
    }

方法四 : O(1)的空间复杂度

前面的方法空间复杂度需要O(n),不是最好的解法,面试中最佳的解答是只使用O(1)的空间复杂度,我们先将原来链表的每一个结点的拷贝放到该结点的next域,然后拷贝的结点的next域指向原节点的next域,这样就会得到一个长度为原来链表2倍的链表,然后怎么找拷贝结点的random域呢,很简单就是原来结点的random的next域,可以看下图理解一下。
这里写图片描述

 //不适用额外空间的做法
    public static Node copyListWithRandom2(Node head){
        if(head == null)return null;

        Node cur = head;
        Node next = null;
        //把每个结点都拷贝一份并且放到这个结点的后面
        while(cur != null){
            next = cur.next;
            cur.next = new Node(cur.value);
            cur.next.next = next;
            cur = next; //循环
        }

        //找到相应拷贝结点的random域
        cur = head;
        Node curCopy = null;
        while(cur != null){
            next = cur.next.next; //原来的下一个
            curCopy = cur.next;
            curCopy.random =  cur.random != null ? cur.random.next : null;
            cur = next;
        }

        //把两个链表拆开
        Node res = head.next;
        cur = head;
        while(cur != null){
            next = cur.next.next;
            curCopy = cur.next;
            cur.next = next;
            curCopy.next = next != null ? next.next : null;
            cur = next;
        }
        return res;

    }

完整测试代码

import java.util.HashMap;

/**
 * 含有随机指针的链表的拷贝(复杂链表的拷贝)
 */
public class CopyListWithRandom {

    private static class Node{
        public int value;
        public Node next;
        public Node random;

        public Node(int value) {
            this.value = value;
        }
    }

    //普通的使用一个HashMap的额外空间为O(n)的方法
    public static Node copyListWithRandom1(Node head){
        if(head == null)return null;
        HashMap<Node,Node>map = new HashMap<>();
        Node cur = head;
        while(cur != null){
            map.put(cur,new Node(cur.value)); // 将当前的节点和这个结点的拷贝结点 put进入map
            cur = cur.next;
        }
        cur = head;
        while(cur != null){
            map.get(cur).next = map.get(cur.next); //设置拷贝节点的next域 为 之前的next域的拷贝
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return map.get(head); //返回拷贝链表的头结点
    }


    //第一种方法其他的写法
    public static Node copyListWithRandom11(Node head){
        if(head == null)return null;
        HashMap<Node,Node>map = new HashMap<>();

        Node copyHead = new Node(head.value);
        map.put(head,copyHead);
        Node q = copyHead,p = head.next;
        while(p != null){  //这个和上面不同的是 顺便一开始就找到了next域
            q.next = new Node(p.value);
            map.put(p,q.next);
            p = p.next;
            q = q.next;
        }
        q.next = null; //将拷贝链表的最后一个结点的next域为null
        //找到拷贝链表的random指针

        p = head; q = copyHead;
        while(p != null){
            q.random = map.get(p.random);
            p = p.next;
            q = q.next;
        }
        return copyHead;
    }


    //上面的方法也可以写成递归的
    public static Node copyListWithRandom111(Node head){
        if(head == null) return null;
        HashMap<Node,Node>map = new HashMap<>();
        Node copyHead = makeMp(head,map);

        Node p = head,q = copyHead;
        while(p != null){
            q.random = map.get(p.random);
            p = p.next;
            q = q.next;
        }

        return copyHead;
    }
    private static Node makeMp(Node head, HashMap<Node, Node> map) {
        if(head == null)return null;
        Node copy = new Node(head.value);
        map.put(head,copy);
        copy.next = makeMp(head.next,map);
        return copy;
    }

    //不适用额外空间的做法
    public static Node copyListWithRandom2(Node head){
        if(head == null)return null;

        Node cur = head;
        Node next = null;
        //把每个结点都拷贝一份并且放到这个结点的后面
        while(cur != null){
            next = cur.next;
            cur.next = new Node(cur.value);
            cur.next.next = next;
            cur = next; //循环
        }

        //找到相应拷贝结点的random域
        cur = head;
        Node curCopy = null;
        while(cur != null){
            next = cur.next.next; //原来的下一个
            curCopy = cur.next;
            curCopy.random =  cur.random != null ? cur.random.next : null;
            cur = next;
        }

        //把两个链表拆开
        Node res = head.next;
        cur = head;
        while(cur != null){
            next = cur.next.next;
            curCopy = cur.next;
            cur.next = next;
            curCopy.next = next != null ? next.next : null;
            cur = next;
        }
        return res;

    }

    public static void printList(Node head) {
        Node cur = head;
        System.out.print("order : ");
        while (cur != null)  {
            System.out.print(cur.value + " ");
            cur = cur.next;
        }
        System.out.println();
        cur = head;
        System.out.print("rand :  ");
        while (cur != null) {
            System.out.print(cur.random == null ? "- " : cur.random.value + " ");
            cur = cur.next;
        }
        System.out.println();
    }


    public static void main(String[] args) {
        Node head = null;
        Node res1 = null;
        Node res2 = null;
        Node res3 = null;
        Node res4 = null;
        System.out.println("-------------测试一-----------");
        printList(head);
        res1 = copyListWithRandom1(head);
        printList(res1);
        res2 = copyListWithRandom11(head);
        printList(res2);
        res3 = copyListWithRandom111(head);
        printList(res3);
        res4 = copyListWithRandom2(head);
        printList(res4);
        printList(head);


        System.out.println("-------------测试二-----------");
        head = new Node(1);
        head.next = new Node(2);
        head.next.next = new Node(3);
        head.next.next.next = new Node(4);
        head.next.next.next.next = new Node(5);
        head.next.next.next.next.next = new Node(6);

        head.random = head.next.next.next.next.next; // 1 -> 6
        head.next.random = head.next.next.next.next.next; // 2 -> 6
        head.next.next.random = head.next.next.next.next; // 3 -> 5
        head.next.next.next.random = head.next.next; // 4 -> 3
        head.next.next.next.next.random = null; // 5 -> null
        head.next.next.next.next.next.random = head.next.next.next; // 6 -> 4

        printList(head);
        res1 = copyListWithRandom1(head);
        printList(res1);
        res2 = copyListWithRandom11(head);
        printList(res2);
        res3 = copyListWithRandom111(head);
        printList(res3);
        res4 = copyListWithRandom2(head);
        printList(res4);
        printList(head);
    }
}

测试结果
这里写图片描述

猜你喜欢

转载自blog.csdn.net/zxzxzx0119/article/details/81088775