「图解大厂面试高频算法题」链表专题-复制复杂链表

「图解大厂面试高频算法题」链表专题-复制复杂链表

PS: 本文为「图解大厂面试高频算法题」专题,主旨是根据“二八法则”的原理,以付出20%的时间成本,获得80%的刷题的收益,让那些想进互联网大厂的人少走些弯路。

PS: 欢迎关注我获取更多大厂面试总结。

原题链接: https://leetcode-cn.com/problems/copy-list-with-random-pointer/

题目介绍

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

示例1

在这里插入图片描述

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

题目解答

方法一:哈希表

思路和算法

本题重点是对这个链表进行深拷贝,也就是说不能改变原有链表,且复制一个新链表出来。首先想到的一个简单的思路就是使用哈希表,这个哈希表的key存储原有链表的节点,哈希表的value存储新链表的节点。

  • 首先遍历整个链表来构建哈希表,遇到的每一个原有链表中的节点就构建一个新节点,同时原有链表的节点作为key,新节点作为value放进哈希表中。
    • 这个步骤操作完之后,哈希表中就有了原有链表节点和新节点的对应关系了。
  • 再遍历一次链表,从哈希表中获取新节点,以及新节点的next和random节点,这样就完成了链表的深拷贝。

这个算法有一个坏处就是需要使用哈希表来存储新旧节点的对应关系,浪费了额外的内存空间,后面还会讲解一个最优的算法思路。

代码实现
class Solution {
    Map<Node, Node> cachedNode = new HashMap<Node, Node>();
    public Node copyRandomList(Node head) {
        for (Node p = head; p != null; p = p.next) {
            cachedNode.put(p, new Node(p.val));
        }
        for (Node p = head; p != null; p = p.next) {
            Node newNode = cachedNode.get(p);
            newNode.next = cachedNode.get(p.next);
            newNode.random =  cachedNode.get(p.random);
        }
        return cachedNode.get(head);
    }
}
复杂度分析
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

方法二:迭代法

思路和算法

这个思路不会使用额外的存储空间来保存新旧节点的对应关系,而是把新旧节点的对应关系保存在原有链表之上,最后再对这个链表进行拆分。

  • 首先遍历一次链表,每遇到一个旧链表的节点,我们就复制一个新的节点出来,再把这个新的节点插在这个旧链表节点的后面。
    • 这次遍历完成之后,链表的长度会变成原来的两倍。链表中奇数节点为旧链表中的节点,偶数节点为新链表中的节点。
    • 而且我们可以通过旧节点,很容易地找到所对应地新节点,新节点也就是这个旧节点的下一个节点。
  • 再次遍历链表,把新节点中的random指针改成正确的新节点。
  • 最后对整个链表进行拆分。
    这个方法的思路理解起来还是有点儿复杂的,同学们可以看下面金刀亲手制作的图解。
图解算法

链表初始状态
在这里插入图片描述

第一次遍历链表之后的状态
其中红色节点表示旧链表中的节点,黄色节点表示新链表中的节点。
每个红色节(旧节点)点的下一个节点都是所对应的新链表中的节点。
在这里插入图片描述
第二次遍历链表之后的状态
在这次遍历中,依次把黄色节点的random指为新的节点。
在这里插入图片描述
拆分链表
最后在对链表进行拆分,这样我们就完成了深拷贝。
在这里插入图片描述

代码实现
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        Node p = head;
        while (p != null) { // 复制链表节点
            Node newNode = new Node(p.val);
            newNode.next = p.next;
            p.next = newNode;
            p = newNode.next;
        }
        p = head;
        while (p != null) { // 修正新节点的random指针
            p.next.random = (p.random == null) ? null : p.random.next;
            p = p.next.next;
        }
        Node dummp = new Node(0);
        Node last = dummp;
        p = head;
        while (p != null) {
            last.next = p.next;
            p.next = p.next.next;
            last = last.next;
            p = p.next;
        }
        return dummp.next;
    }
}
复杂度分析
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知春路金刀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值