剑指 Offer 35. 复杂链表的复制

题目

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

思路
  1. 使用哈希表/散列表(hashmap)的方式解题
    大致思路如下:
    将整个复制的过程分为两部分:节点的复制,指针域的复制
    1、首先进行节点的复制:
    设置一个指针cur对head链表进行遍历,遍历的同时,完成对于head链表的节点的复制(此时,对于新创建的节点,只设置它的val属性的值)
    采用哈希表(hashmap),来存储head链表中的节点与其对应的新创建的节点之间的,对应关系(便于2、中 进行指针域的指向的复制)
    2、进行节点的指针域(next域、random域)的复制:
    遍历head链表,对于它的每个节点cur的指针域(cur.next、cur.random),通过hashmap的get方法,获取与之对应的新创建的节点;对于cur节点本身也使用get方法,获取与之对应的新创建的节点。=>使得与cur对应的新创建的节点 的指针域(next、random)均指向它们各自的节点(也是新创建的节点):
    map.get(cur).next = map.get(cur.next);
    map.get(cur).random = map.get(cur.random);
    ==>因为此时已经将head链表中的所有节点都进行过复制,并且存储在hashmap中了,所以在此时进行指针域的设置,就不会存在所指向的节点不存在的情况了。

  2. 先将新的链表存储head链表中,最后进行两个链表的分离
    =>按照3步来进行:
    1、在链表head中完成对于该链表中所有节点的复制(不包含random指针域的复制)
    2、在链表head中,实现对于复制节点的random指针域的赋值
    3、将复制的节点从head链表中抽离出来,并将head链表也还原成原来的样子

实现
  1. 使用哈希表/散列表(hashmap)的方式解题
class Solution {
    public Node copyRandomList(Node head) {
        //先考虑特殊情况:head的链表为空
        if(head == null){
            return null;
        }
        //常规情况的处理方式
        Node cur = head;//用于遍历head链表的指针
        HashMap<Node,Node> map = new HashMap();
        //1、首先进行节点的复制:
        while(cur != null){//没有遍历完head链表
            map.put(cur, new Node(cur.val));//完成复制操作
            cur = cur.next;//遍历操作
        }
        //2、进行节点的指针域(next域、random域)的复制:
        cur = head;//开始新一轮,对于head链表地遍历
        while(cur != null){
            map.get(cur).next = map.get(cur.next);//指针域的复制
            map.get(cur).random = map.get(cur.random);//指针域的复制
            cur = cur.next;//遍历
        }
        return map.get(head);//返回的是head节点 对应的复制得到的节点(新链表的头节点)
    }


}

时间复杂度:O(n)
空间复杂度:O(n)

  1. 先将新的链表存储head链表中,最后进行两个链表的分离

==>代码写起来比使用哈希表的方式简单,但是:空间复杂度低,为常数阶O(1)

class Solution {
    public Node copyRandomList(Node head) {
        //按照3步来进行:
        //1、在链表head中完成对于该链表中所有节点的复制(不包含random指针域的复制)
        head = copyBase(head);//返回的完成节点赋值的链表,仍用head指针指向它
        //2、在链表head中,实现对于复制节点的random指针域的赋值
        head = copyRandom(head);
        //其实前两个方法有没有返回值都可以,因为head指针的指向一直没有改变,且是引用类型=>不论head所在的链表如何改变=>head仍然指向该指针=>可以将该链表的改变带回
        //3、将复制的节点从head链表中抽离出来,并将head链表也还原成原来的样子
        Node copyHead = copyDivide(head);

        return copyHead;
    }

    //1、在链表head中完成对于该链表中所有节点的复制(不包含random指针域的复制)
    public static Node copyBase(Node head){
        //设置一个在head链表中的指针(指向head链表的头节点),实现对于head链表的遍历
        Node list = head;
        while(list != null ){
            //在遍历过程中,对于head链表中的每一个不为null的节点,都要进行复制操作
            //1、定义一个新节点temp;2、将list节点的信息复制给它temp;3、实现该新节点temp的插入操作;4、将list节点指向对应的head链表中的下一个节点
            Node temp = new Node(list.val);//实现复制的节点

            //实现对于节点插入操作必须要进行的指针的修改
            temp.next = list.next;
            list.next = temp;

            //更新list节点
            list = temp.next;
        } 
        return head;
    }


    //2、在链表head中,实现对于复制节点的random指针域的赋值
    public static Node copyRandom(Node head){
        //定义一个指针,指向head链表的头节点,实现对于head链表的遍历
        Node list = head;
        //定义一个指针,指向复制出来的第一个节点(如果该节点存在的话),实现对于复制得到的节点的遍历
        Node cur = null;//初值为null,是因为如果head链表为空的话,直接使用cur = head.next会出现:空指针异常
        //当head节点不为空的时候(否则会出现空指针异常)
        while(list != null){
            cur = list.next;//定位好cur指针指向的元素

            //因为list的random域,指向指向链表中的任意节点或者 null
            //当list.random指向的不是null时,=>相对应的,cur的random域,指向的是:list的random域指向的节点,复制出来的对应的节点=>位于该节点list.random后面的节点
            //当list.random指向的不是null时,=>cur的random域指向的也将是null(不用做出改变,因为最初的值即为null)。但应该与上面的情况分开!因为null.next会出现空指针异常!!!!!!
            if(list.random != null){
                cur.random = list.random.next ;
            }

            //实现链表的向前遍历
            list = cur.next;
        }
        return head;
    }


    //3、将复制的节点从head链表中抽离出来,并将head链表也还原成原来的样子
    public static Node copyDivide(Node head){
        //定义一个指针,指向head链表的头节点,实现对于head链表的遍历
        Node list = head;
        //定义一个指针,指向复制出来的第一个节点(如果该节点存在的话),实现对于复制得到的节点的遍历
        Node cur = null;//初值为null,是因为如果head链表为空的话,直接使用cur = head.next会出现:空指针异常
        Node copyHead = null;//定义这个指针,指向复制之后得到的链表的头节点,最后也是return它。刚开始这个指针不能直接赋值head.next,因为,如果head指针为空的话,会出现空指针异常;不过此时正好return 它,也应该是null值,正好对应默认情况

        //开始进行循环
        while (list != null){//没有遍历完head链表
            //先写出copyHead赋值的情况,这种情况只出现一次
            if(copyHead == null){
                //copyHead指向 第一个节点
                copyHead = head.next;
                //并且cur节点不用处理前面节点指向它的指针
                cur = list.next;
            }else{
                //需要考虑前一个cur节点,指向此次cur节点的指针问题
                cur.next = list.next;
                cur = list.next;
            }
            //常规情况的操作(对于list指针)
            list.next = cur.next;//先修改指针指向
            list = list.next;//然后接着向后遍历
        }
        return copyHead;
    }


}

时间复杂度:O(n)
空间复杂度:O(1)


引用别人的一句话,送给自己吧!
技术的突飞猛进往往是自然发生的。 你在某个夜晚苦熬一个知识点时, 不会觉得自己突飞猛进; 只有在多年后的某日, 熟练地给别人讲解这个知识点后, 内心才会小小地波动一下, 猛然忆起当年深夜中的青灯一盏。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值