IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> LeetCode 138. 复制带随机指针的链表 -> 正文阅读

[数据结构与算法]LeetCode 138. 复制带随机指针的链表


这道题,需要对一个单链表进行深拷贝,特殊的地方在于,每个节点,除了 next指针,还带有一个 random指针,这个 random指针会随机指向整个链表中的某一个节点。

若是普通单链表,则依次遍历每个节点,每次新建一个节点并拷贝原节点中的值val即可。每个节点都带有一个随机指针,造成的难点在于:如果这个random随机指针指向的节点,先前已经拷贝过了,则需要复用先前拷贝的那个节点。

解法一:借助哈希表

所以比较直观的想法就是,用一个Map来记录新旧节点的映射关系(key为旧的节点,value为新的节点),当准备对一个旧的节点进行拷贝时,先查一下Map,如果先前已经拷贝过,则直接从Map中取出对应的新节点;若没有拷贝过,则新建一个节点,并插入到Map

通过引入一个哈希表,来完成整个拷贝过程,这种做法的额外空间复杂度是 O ( n ) O(n) O(n),时间复杂度是 O ( n ) O(n) O(n)

两次遍历

一个最简单的做法是,两次遍历。第一次遍历原链表,对每个旧的节点,依次新建一个节点,并插入到Map;第一次遍历结束后,我们已经得到了全部的新节点。接下来只要把这些新的节点按照顺序连接起来即可;则,第二次遍历原链表,每次从Map中取出需要的节点即可。

class Solution {
    public Node copyRandomList(Node head) {
        Map<Node, Node> map = new HashMap<>();
        // 第一次遍历, 先把所有新节点构造出来
        for (Node cur = head; cur != null; cur = cur.next) {
            map.put(cur, new Node(cur.val));
        }
        // 第二次遍历, 将所有的新节点连接起来
        for (Node cur = head; cur != null; cur = cur.next) {
            Node newNode = map.get(cur);
            newNode.next = map.get(cur.next);
            newNode.random = map.get(cur.random);
        }
        return map.get(head);
    }
}

递归

除此以外,可以用递归的思路来做。设一个函数copy(Node x),其含义是深拷贝以节点x开头的整个链表,并返回新的链表头。仍然需要借助一个Map来存放已经构造出来的节点

class Solution {
    public Node copyRandomList(Node head) {
        return copy(head, new HashMap<>());
    }
    
    private Node copy(Node x, Map<Node, Node> map) {
        if (x == null) return null;
        // 已经构造过, 直接返回
        if (map.containsKey(x)) return map.get(x);
        // 否则, 构造一个新的节点
        Node newNode = new Node(x.val);
        // 将新构造的节点放入 map
        map.put(x, newNode);
        // 递归构造 next 和 random
        newNode.next = copy(x.next, map);
        newNode.random = copy(x.random, map);
        return newNode;
    }
}

解法二:节点拆分

上面的做法都需要借助哈希表,额外空间复杂度是 O ( n ) O(n) O(n),能不能有额外空间复杂度为 O ( 1 ) O(1) O(1)的做法呢?答案是有的。
仔细思考一下,我们为什么需要一个哈希表?
我们仅仅是需要借助哈希表,来找到某一个旧的节点,对应的新节点。
那么,如果我们将每一个新节点,插入到其对应的旧节点的后面,这样一来,不需要借助哈希表,也能够根据旧节点,定位到其对应的新节点。
在这里插入图片描述
第一次遍历,将所有新节点构造出来,并插入到原链表当中。
第二次遍历,对每个新节点,填补其random指针
第三次遍历,将新旧链表进行拆分

class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) return null;
        // 构造全部新节点
        for (Node cur = head; cur != null; cur = cur.next.next) {
            Node newNode = new Node(cur.val); // 构造一个新节点
            newNode.next = cur.next; // 将新节点插入到当前节点后面
            cur.next = newNode;
        }
        // 填补新节点的 random 域
        for (Node cur = head; cur != null; cur = cur.next.next) {
            Node newNode = cur.next;
            if (cur.random != null) newNode.random = cur.random.next;
        }
        // 拆分两个链表
        Node res = head.next;
        for (Node cur = head; cur != null; cur = cur.next) {
            Node newNode = cur.next;
            cur.next = cur.next.next;
            if (newNode.next != null) newNode.next = newNode.next.next;
        }
        return res;
    }
}

关于力扣官方题解中提到的:“读者可自行尝试将3次遍历缩短为2次遍历”
在这里插入图片描述
也就是尝试将填补random域和拆分链表进行合并。

经过尝试后发现行不通。原因如下:

因为遍历是从左往右的,那么在遍历到中间某个位置i时,i之前的部分,新旧链表已经被拆分开,而对于i之后的新节点,需要填补其random域,而其random域完全有可能指向i之前的节点。而i之前的部分已经做了拆分,对于i之前的节点,无法再根据旧节点找到对应的新节点。也就是说,i之前的部分,新旧节点之间的联系已经断掉了。所以无法填补新节点的random域。

但是,如果题目允许修改原链表的结构的话,则可以通过2次遍历得到新链表。

在遍历时,仅对新节点进行拆分,即,只改变新节点的next指针,而不把旧节点指向新节点的next指针断开。这样能够保证无论何时都能根据旧节点找到对应的新节点。即能顺利完成对新节点的random域的填充。
只是,这样最后的链表结构将变成下面这样子:
在这里插入图片描述
这样,只能把新链表拆分出来,而原链表的结构则被改变了。

代码如下

class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) return null;
        // 构造全部新节点
        for (Node cur = head; cur != null; cur = cur.next.next) {
            Node newNode = new Node(cur.val); // 构造一个新节点
            newNode.next = cur.next; // 将新节点插入到当前节点后面
            cur.next = newNode;
        }
        // 填补新节点的 random 域 + 拆分
        for (Node cur = head; cur != null; ) {
            Node nextCur = cur.next.next;
            Node newNode = cur.next;
            if (cur.random != null) newNode.random = cur.random.next;
            if (newNode.next != null) newNode.next = newNode.next.next;
            cur = nextCur;
        }
        // 拆分两个链表
        return head.next;
    }
}

尝试提交了一下这种做法的代码,发现修改原链表的结构是不允许的
在这里插入图片描述

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-04-23 11:02:21  更:2022-04-23 11:06:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 8:31:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码