0
点赞
收藏
分享

微信扫一扫

复制带有随机指针rand的链表

复制带有随机指针rand的链表

提示:这个跟单独复制一个链表有区别


文章目录

题目

给你一个链表,链表有next指针,这是主链,当然也有rand指针,但是rand指针可能是随机指向null或者其他某个点的。
请你复制带有rand指针的链表,返回复制链表的头节点。


一、审题

示例:下图总的链表,除了next把1-2-3-4串接起来了
1节点的rand指向了3节点
2节点的rand指向了自己
3和4的rand指向了null
请你复制一个同样的链表,然后返回新链表的头节点。
在这里插入图片描述


二、解题

又是一个链表的题目,链表是整个都要遍历的o(n)时间复杂度逃不了,唯独就是看看能否优化空间复杂度
本题涉及的链表的节点,数据结构Node多了rand

public static class Node{
        public int value;
        public Node next;
        public Node rand;
        public Node(int v){
            value = v;
        }
    }

造出一根链表来:

//复习:造链表
    public static Node createLinkNode(){
        Node n1 = new Node(1);
        Node n2 = new Node(2);
        Node n3 = new Node(3);
        n1.next = n2;
        n1.rand = null;
        n2.next = n3;
        n2.rand = n1;
        n3.rand = n1;
        
        return n1;
    }

造出来的链表是这样的:

1
其rand指针为:null
2
其rand指针为:1
3
其rand指针为:1

笔试求AC,耗费额外空间复杂度o(n)

既然是复制,那就可以额外用空间,先复制相同的节点,然后再套rand指针
这里的话,我们得知道每个节点cur,它next连着谁?也要直到rand它连着谁?
故复制出来一个新的节点,咱得知道旧节点都得情况

(1)所以建议用哈希表存好新旧节点之间的对应关系
通过旧节点访问其next和rand指针,来搞定新节点
哈希表中:key:旧节点,value:新节点
在这里插入图片描述
(2)这样的话,遍历原链表,拿到旧节点key,就能通过get函数拿到新节点,旧节点怎么连next和rand指针,新节点就怎么连其next和rand指针。
比如:上面的1节点,新1的next应该指向谁呢?当然是旧1的next指向的旧2点,所对应的新2点
代码这么描述:map.get(旧1点).next 【新1点】= map.get(旧1.next【即旧2点】) 【即新2点】;
上图绿色2就是新2点,粉色3就是新3点

总之,新点的连接方式,全靠旧点的连接指引
手撕代码:

//复习:笔试AC代码,哈希表
    //所以建议用哈希表存好新旧节点之间的对应关系
    //通过旧节点访问其next和rand指针,来搞定新节点
    //哈希表中:key:旧节点,value:新节点
    public static Node copyPen(Node head){
        if (head == null) return head;

        //复制一下:哈希表中:key:旧节点,value:新节点
        HashMap<Node, Node> map = new HashMap<>();
        Node cur = head;
        while (cur != null){
            map.put(cur, new Node(cur.value));
            cur = cur.next;
        }

        //(2)这样的话,遍历原链表,拿到旧节点key,就能通过get函数拿到新节点,
        // 旧节点怎么连next和rand指针,新节点就怎么连其next和rand指针。
        cur = head;
        while (cur != null){
            map.get(cur).next = map.get(cur.next);//新节点next,指向旧节点cur的下一个点,对应的复制节点
            map.get(cur).rand = map.get(cur.rand);//同理,rand也是根据旧节点rand指向那个点的复制节点
            cur = cur.next;
        }

        return map.get(head);//旧链表头对应的复制节点,就是新链表
    }

面试求最优解,优化空间复杂度o(1)

面试就要节约空间,争取做到o(1)
那么就只能在原链表上操作了

这样做:
(1)直接在原链表每一个节点cur后面复制一个节点copyCur,当然,先要记好,下次操作的节点next=cur.next;
(2)让cur的next断开,指向新节点copyCur,然后copyCur的next指向next节点。
(3)整个链表都复制好以后,检查新节点的rand指针,原链表的rand指向哪里,新节点的rand就指向原链表对应复制节点的rand那。
(4)然后将新旧链表断开,各自连各自的
看图
(1)(2)(3)两步整好以后:
在这里插入图片描述
(4)之后:各自断开,红色那条就是旧链表,粉色那条就是新链表。
在这里插入图片描述
好,手撕代码,链表的题目就是coding要仔细

//复习:面试代码:
    //(1)直接在原链表每一个节点cur后面复制一个节点copyCur,当然,先要记好,下次操作的节点next=cur.next;
    //(2)让cur的next断开,指向新节点copyCur,然后copyCur的next指向next节点。
    //(3)整个链表都复制好以后,检查新节点的rand指针,原链表的rand指向哪里,新节点的rand就指向原链表对应复制节点的rand那。
    //(4)然后将新旧链表断开,各自连各自的
    public static Node copyFace(Node head){
        if (head == null) return head;

        //(1)直接在原链表每一个节点cur后面复制一个节点copyCur,当然,先要记好,下次操作的节点next=cur.next;
        //(2)让cur的next断开,指向新节点copyCur,然后copyCur的next指向next节点。
        Node cur = head;
        Node next = cur.next;//下次要去的地方
        while (cur != null){
            Node copyNode = new Node(cur.value);//复制cur
            cur.next = copyNode;//断开,重新指新节点
            copyNode.next = next;//中间插入一个,挂好旧节点
            cur = next;
            if (cur != null) next = cur.next;//操作下一个
        }
        //(3)整个链表都复制好以后,检查新节点的rand指针,原链表的rand指向哪里,
        // 新节点的rand就指向原链表对应复制节点的rand那。
        cur = head;
        next = cur.next.next;//跳两步
        while (cur != null){
            Node copyNode = cur.next;
            copyNode.rand = cur.rand == null ? null : cur.rand.next;//旧节点rand所指的next是复制节点
            cur = next;//操作下一个旧节点
            if (cur != null) next = cur.next.next;//跳两步
        }
        //(4)然后将新旧链表断开,各自连各自的
        cur = head;
        next = cur.next.next;//跳两步
        Node ans = cur.next;//新链表的头
        while (cur != null){
            Node copyNode = cur.next;
            cur.next = next;//断开,跳指旧节点
            if (next != null) copyNode.next = next.next;//跳指,新节点
            cur = next;//操作下一个
            if (cur != null) next = cur.next.next;
        }
        //当中途cur或者next有遇到null,他们是没指针的。

        return ans;
    }

代码是复杂了点,但是空间复杂度o(1)必然带来复杂的操作程序
测试一下:

//测试
    public static void test2(){
        Node head = createLinkNode();
        Node cur = copyPen(head);
        while (cur != null){
            System.out.println(cur.value);
            if (cur.rand == null) System.out.println("其rand指针为:null");
            else System.out.println("其rand指针为:" + cur.rand.value);

            cur = cur.next;
        }
        System.out.println("---------------");
        Node head2 = createLinkNode();
        Node cur2 = copyFace(head2);
        while (cur2 != null){
            System.out.println(cur2.value);
            if (cur2.rand == null) System.out.println("其rand指针为:null");
            else System.out.println("其rand指针为:" + cur2.rand.value);

            cur2 = cur2.next;
        }
    }

    public static void main(String[] args){
//        test();
        test2();
    }

俩代码的结果都没问题:

1
其rand指针为:null
2
其rand指针为:1
3
其rand指针为:1
---------------
1
其rand指针为:null
2
其rand指针为:1
3
其rand指针为:1

总结

提示:重要经验:

1)关于链表的操作代码,思想很简单,主要就是coding复杂了点,把细节抠清楚,才行
2)将节点复制,挂中中间,又断开,就需要cur和next配合,这是链表操作行为中的常规骚操作。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

举报

相关推荐

0 条评论