【剑指Offer1】复杂链表的复制

题目:

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

例如

 

 

 

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

 

思路:

一开始没想通题目啥意思,后面明白了其实是自己引用类型学的不好。定义一个新的node用原有链表进行赋值,只是给原有链表的节点增加了一个引用,并不是复制了该节点。

真正的复制,应该是new一个全新的空node,用对应原链表的val进行赋值,当然next和random也必须指向自己创建的节点。

到这里可以明确链表至少需要遍历两遍。因为首次创建新链表节点时,random指向的节点可能没有创建,必须要将新链表的节点全部创建好串起来,才可以再遍历更新random的指向。

那么问题就在于,怎么把旧链表的节点关系,在新链表两个全新的节点中重现?

A代表新链表节点,B代表旧链表节点,即需要找到A.random和B.random的对应关系,进一步想到,只需要拥有A表每个节点与之对应的B表节点的关系映射,问题就可以解决,故想到HashMap。

在遍历创建B表的同时,以A表节点作为key,B表节点作为value,将AB的对应关系存在map中。第二次遍历更新random指针时,只要将A对应的B取出,之后将A.random对应的B节点赋值给B.random即可

代码如下:

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
        // 处理边界值
        if (head == null) {
            return null;
        }
        Map<Node, Node> nmap = new HashMap<>();
        Node newHead = new Node(head.val);
        Node curNode = head;
        nmap.put(head, newHead);
        // 生成基础链表与映射map
        while (curNode.next != null) {
            // 生成新节点
            Node newNode = new Node(curNode.next.val);
            // 取出上一个节点
            Node preNode = nmap.get(curNode);
            // 将新节点与上一个节点相连;
            preNode.next = newNode;
            // 在map中更新上一个节点,存入新节点
            nmap.put(curNode.next, newNode);
            curNode = curNode.next;
        }
        // 更新random指针
        curNode = head;
        while (curNode != null) {
            // 查询random节点
            Node randomNode = curNode.random;
            if (randomNode != null) {
                nmap.get(curNode).random = nmap.get(randomNode);
            } else {
                nmap.get(curNode).random = null;
            }
            curNode = curNode.next;
        }
        return newHead;
    }
}

 

总结:

当然还是有更牛的大佬,用了更少的空间,采用了拼接/拆分链表的方法解答,代码如下:

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
//拼接+拆分
/**
1 复制各节点,构建拼接链表:
设原链表为node1→node2→⋯ ,构建的拼接链表如下所示:
node1→node1 new→node2→node2 new→⋯

2 构建新链表各节点的 random 指向:
当访问原节点 cur 的随机指向节点 cur.random 时,对应新节点 cur.next 的随机指向节点为 cur.random.next 。

3  拆分原 / 新链表:
设置 pre / cur 分别指向原 / 新链表头节点,遍历执行 pre.next = pre.next.next 和 cur.next = cur.next.next 将两链表拆分开。
返回新链表的头节点 res 即可。

 */
class Solution {
    public Node copyRandomList(Node head) {
        if(head==null) return null;
        Node cur =head;
        //1 复制各节点,构建拼接链表
        while(cur!=null){
            Node tmp=new Node(cur.val);
            tmp.next=cur.next;
            cur.next=tmp;
            cur=tmp.next;
        }
        //2 构建新链表各节点的 random 指向
        cur=head;
        while(cur!=null){
            if(cur.random!=null){
                cur.next.random=cur.random.next;
            }
            cur=cur.next.next;
        }
        //3 拆分原 / 新链表
        cur=head.next;
        Node pre=head,res=head.next;
        while(cur.next!=null){
            pre.next=pre.next.next;
            cur.next=cur.next.next;
            pre=pre.next;
            cur=cur.next;
        }
        pre.next=null;//单独处理原链表尾结点
        return res;
    }
}

标准答案,学习一个。

就是通过“”自己的下一个是自己的复制“”,做到了映射关系的管理,很强!

 

posted @ 2022-01-09 00:52  ProbablyTrue  阅读(32)  评论(0编辑  收藏  举报