剑指Offer刷题总结-复杂链表的复制

复杂链表的复制

今天重刷剑指offer,又遇到这道题了,今天绝对的要把它搞的明明白白。
奋战一天,完成任务。
不太写博客,都在有道上面,以后坚持下,多多输出,文中有写的不好的地方希望大佬们轻喷。

题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。
在这里插入图片描述

思路分析

这道题可能刚开始做会懵逼,但是没关系从暴力开始,然后逐步优化

1.暴力-------时间复杂度O(n²),空间复杂度O(1)
复制的过程分为两步

  • 复制原始链表的每一个结点,并用 next 指针连接
  • 复制原始链表的每一个结点 random 指针所指向的结点

第一步很容易,创建个头节点,然后移动指针就可以了
第二步稍稍有点复杂(其实也不是特复杂😂),什么样的思路呢

  • 拿到原始链表的每个结点的 random指针指向的节点
  • 每次从 新链表中根据 label 值找到这个节点,然后将 新链表的节点的 random 指针指向这个节点
  • 链表指针后移,重新定位下一个节点 random指针指向的位置,这个时候就需要回到新链表的第一个节点

但是存在问题
就是 random 指针可能为空,为空的话那么直接就将 新链表当前节点的 random 指针置空,退出循环

对于一个含有 n 个节点的链表,由于定位每个节点的 random 都需要从链表头节点开始经过 O(n) 步才能找到,因为这种方法的总时间复杂度是 O(n²)

public RandomListNode Clone(RandomListNode pHead){
        if(pHead==null){
            return null;
        }

        //1.创建头节点,方便复制
        RandomListNode newList = new RandomListNode(-1);
        RandomListNode res = newList;
        RandomListNode head = pHead;

        //2.复制原始链表中next
        while(head!=null){
            RandomListNode cur = new RandomListNode(head.label);
            res.next = cur;
            res = cur;
            head = head.next;
        }

        //3.复制原始链表中的random
        RandomListNode node = newList.next;
        RandomListNode temp = null;
        head = pHead;
        while(node!=null){
            RandomListNode randomNode = head.random;
            temp = newList.next;
            while(temp!=null){
                if(randomNode!=null){
                    if(temp.label==randomNode.label){
                        node.random = temp;
                    }
                    temp=temp.next;
                }else{
                    node.random=null;
                    temp = null;
                }
            }
            head = head.next;
            node = node.next;
        }
        return newList.next;
    }

2.HashMap-----时间复杂度O(n),空间复杂度O(n)
暴力法 n² 的原因就在于每次需要从链表头找到 random 指向的那个节点,但是想把时间复杂度降下来,可以从空间这方面考虑
使用 HashMap 也是分为两步

  • 把原始链表的每个节点存到 哈希表中 ,key 是原始链表的结点,value 是新复制的结点
  • 复制原始链表中对应结点的 next 和 random 指向

这里想一想,为什么要放到 哈希表 中了呢?
放入之后,那么就可以根据 key.next 或者 key.random 很快的找到节点的下一个 或者 随机指向的了,那么就避免了暴力解法中每次从链表头去找,降低了时间复杂度

			map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);

这两行代码是整个过程中最妙的两行代码
慢慢体会~~~

public RandomListNode Clone(RandomListNode pHead){
        //1.判断链表是否合法
        if(pHead==null){
            return null;
        }

        //2.创建 HashMap 来存储
        HashMap<RandomListNode,RandomListNode> map = new HashMap<>();
        RandomListNode cur = pHead;

        //3.把原始链表中的每个节点存储 HashMap
        //其中 Key 是原始节点,value 是复制节点
        while(cur!=null){
            map.put(cur,new RandomListNode(cur.label));
            cur = cur.next;
        }

        //4.复制原始链表中的 next 和 random 指向
        cur = pHead;
        while(cur!=null){
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }

        //5.返回新链表的头节点
        return map.get(pHead);
    }

3.更巧妙的方法,复制next、复制random、拆分-----时间复杂度O(n),空间复杂度O(1)
分为三步,但是这种方法不具有普遍性,只是针对这个题最妙的解法了

  • 在原始链表的每个节点后面插入复制的节点
    在这里插入图片描述
  • 对复制的节点的 random 进行连接
    ①这一步原始链表上的节点 N 指向的 random 节点 其实就在 复制节点 N1 指向的 random 节点的前面
    ②所以就是 N1.random = N.random.next
    在这里插入图片描述
  • 接下来就是拆分的过程了,将链表分为 原始链表 和 复制链表 两部分
    在这里插入图片描述
//4.拆分
        head = pHead;
        RandomListNode newHead = head.next;
        while(head.next != null){
            RandomListNode node = head.next;
            head.next = node.next;
            head = node;
        }

拆分是最后一步

自己写一直陷入了newHead、head 的关系中
但是看了大佬的代码明白了
拆分的过程可以对原始节点、复制节点一个一个的来拆分
先拆分原始链表,然后通过 head = node 这行代码继续拆分 复制链表

public RandomListNode Clone(RandomListNode pHead){
        //1.链表是否合法
        if(pHead==null){
            return null;
        }

        //2.将复制节点插入到原始节点的后面
        RandomListNode head = pHead;
        while(head!=null){
            RandomListNode cur = new RandomListNode(head.label);
            RandomListNode temp = head.next;
            head.next = cur;
            cur.next = temp;
            head = cur.next;
        }

        //3.复制节点的random指向
        head = pHead;
        while(head!=null){
            RandomListNode copyNode = head.next;
            if(head.random!=null){
                copyNode.random = head.random.next;
            }
            head = copyNode.next;
        }

        //4.拆分
        head = pHead;
        RandomListNode newHead = head.next;
        while(head.next != null){
            RandomListNode node = head.next;
            head.next = node.next;
            head = node;
        }
        return newHead;
    }

总结
从这个题中学到了很多,解决链表这块的问题先是暴力,然后考虑优化时间复杂度,这个时候就可以去考虑用空间来降低时间复杂度,一般的话就是 HashMap 或 HashSet 。
然后呢
再去考虑降低空间复杂度,达到O(1)的空间复杂度,一般的做法是采用双指针,可能是快慢指针、也可能是前后指针
但是这个题第三种复制+拆分的思路自己还是第一次见(可能是我刷题还不够多),针对这个题来说它就很妙、很妙
自己还得继续学习呀!!!
以上的图来自:链表的图以及借鉴大佬的代码

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值