问题描述
A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.
Return a deep copy of the list.
原问题链接:https://oj.leetcode.com/problems/copy-list-with-random-pointer/
问题分析
这个问题粗看起来确实不好解决,因为这不是一个单纯的linked list的拷贝,它里面还包含了一些随机指向某些节点的指针。对于链表里指向每个元素后面的元素都还好说,只要沿着原来的地方一个个的拷贝过去就可以。可是这些随机指向的可能就跳到不知道哪里去了。在原来的说明里已经给出了linked list里面每个节点的定义:
class RandomListNode { int label; RandomListNode next, random; RandomListNode(int x) { this.label = x; } }
初步探讨
我们先假定有一个如下图的随机链表:
如果我们不考虑随机指向的链接的话,我们每次访问原链表中一个元素时,可以直接建立一个对应的拷贝。当原来的元素要指向下一个元素时,我们再建一个下一个元素的拷贝,然后将原来的元素指向新建的下一个元素。这个过程类似于一个递归的过程,当然,通过这个过程我们可以建立一个如下图这样的拷贝效果:
这是在仅仅考虑指向后续元素引用的情况下。如果一旦我们创建好这个之后,随机引用的指针就反而不好处理了。假如在原来的链表中,有第一个元素的随机指针指向第三个元素,我在拷贝的链表里怎么知道呢?因为一旦拷贝出来之后,在新拷贝的链表里是没法知道怎么对应的。难道我们还要去专门建立一个新建元素和原来链表元素的一一映射吗?而且就算我们去建立这么一个映射,难道用Map就一定能解决?如果原来的链表里有值相同的元素呢?会不会没法区分?看来就这么直接复制过来的办法不可行。
换一种思路
其实,在前面我们复制每个链表节点的时候,我们只要从头开始,每次必然可以构造出该节点对应的拷贝。如果我们每次新建的链表节点不急着放到外面来拼装成一个链表,而是先放到每个对应链表节点的后面呢?比如下图的这样:
我们用更深蓝色的节点表示拷贝节点,这样它们就形成了一个原节点和拷贝节点相间的这么一个结构。现在,我们再来考虑随机指针。因为在原来节点中,随机指针指向了某个节点,在这个增加了拷贝节点之后的链表里,其实原来的指针是没有任何变化的。但是因为我们新加入的拷贝节点都是在对应节点的后面一个。这不就正好方便我们来处理随机指针了吗?
因为原来对于某个节点它随机指针指向了一个节点,而我拷贝节点是原节点的后面一个。那么对应拷贝节点的随机指针不就是对应原来节点所指向的随机指针后面的那个吗?我们把随机指针加上来考虑的话,则我们新拷贝的节点和原来节点的关系如下图:
按照这个关系,我们处理随机指针就可以按照如下的方式:
1. 每次碰到一个原有节点的时候,假定原节点为a, 先记录一下它后面的那个拷贝节点,假设拷贝节点为b。
2. 将a节点所指向的随机节点后面那个元素,即a.random.next设置为拷贝节点的随机指针目的。也就是b.random = a.random.next。
在完成了上述步骤之后,我们就需要将上图中拷贝的元素部分再剥离出来。因为随机指针在前一步都已经设置好了,它们不会受到影响。所以这里的剥离也就很简单了,设置一前一后两个指针,每个都跳一个指向后面的元素就可以了。
综合
综合上面的讨论,这个问题的解决步骤如下:
1. 遍历原有链表,在每个原来的节点后面增加一个拷贝节点。
2. 根据原节点的随机指针设置拷贝节点的随机指针。
3. 剥离出所有拷贝节点。
按照这个思路,第一步的代码实现如下:
RandomListNode copy = head; while(copy != null) { RandomListNode node = new RandomListNode(copy.label); node.next = copy.next; copy.next = node; copy = node.next; }
因为我们需要在每个节点后面创建一个拷贝节点,同时不希望修改原有的初始节点,所以开始的时候创建了一个head节点的拷贝copy。每次将新建的node插入到copy节点后面。在设置完了第一步之后我们需要再从拷贝节点的第一个开始去设置随机指针。它的实现如下:
copy = head; while(copy != null && copy.next != null) { if(copy.random != null) copy.next.random = copy.random.next; copy = copy.next.next; }
因为每次要跳过它后面的节点,所以这里copy = copy.next.next;
剩下的就是第三步,剥离拷贝节点:
copy = head; RandomListNode cur = head.next; RandomListNode tmp = cur; while(copy != null && tmp != null) { copy.next = tmp.next; copy = copy.next; if(tmp.next != null) { tmp.next = tmp.next.next; } tmp = tmp.next; }
这个剥离的过程也并不复杂,首先将拷贝节点前面的元素指向它后面的元素。然后再将这个拷贝节点往后面跳一个。将上述的几个步骤结合起来,就得到如下的代码:
public class Solution { public RandomListNode copyRandomList(RandomListNode head) { if(head == null) return null; RandomListNode copy = head; while(copy != null) { RandomListNode node = new RandomListNode(copy.label); node.next = copy.next; copy.next = node; copy = node.next; } copy = head; while(copy != null && copy.next != null) { if(copy.random != null) copy.next.random = copy.random.next; copy = copy.next.next; } copy = head; RandomListNode cur = head.next; RandomListNode tmp = cur; while(copy != null && tmp != null) { copy.next = tmp.next; copy = copy.next; if(tmp.next != null) { tmp.next = tmp.next.next; } tmp = tmp.next; } return cur; } }
方法二
前面讨论的拷贝随机指针的方法虽然效率比较可观,只是推导的思路相对有点复杂。实际上,结合链表的创建和拷贝,我们还有另外一种思路。
在不考虑随机指针的情况下,我们只需要在一个链表从头到尾遍历的时候同时创建一个个对应的节点。新链表的创建可以通过创建一个临时节点,它指向新建链表的头节点。这样我们在遍历完链表之后可以找到这个头节点。
当然,这样对于一个简单的链表拷贝已经够了。可是还有一些随机链表要考虑。这该怎么解决呢?我们可以在前面遍历原链表的同时建立一个Map,每次将原链表节点和对应新建的节点加入到map中。在创建完包含有next的链表元素之后,我们再一次遍历两个链表。每次遍历原来链表的时候判断它的random指针,如果这个指针非空,则将对应新建链表的random指针指向map里对应的项。
于是,按照这个思路,我们可以得到如下的代码:
/** * Definition for singly-linked list with a random pointer. * class RandomListNode { * int label; * RandomListNode next, random; * RandomListNode(int x) { this.label = x; } * }; */ public class Solution { public RandomListNode copyRandomList(RandomListNode head) { RandomListNode l1 = new RandomListNode(0); RandomListNode l2 = new RandomListNode(0); RandomListNode pre2 = l2; l1.next = head; Map<RandomListNode, RandomListNode> map = new HashMap<>(); while(head != null) { RandomListNode copy = new RandomListNode(head.label); pre2.next = copy; map.put(head, copy); head = head.next; pre2 = pre2.next; } head = l1.next; pre2 = l2.next; while(head != null) { if(head.random != null) { pre2.random = map.get(head.random); } head = head.next; pre2 = pre2.next; } return l2.next; } }
总结
总的来说,这个问题相对来说复杂一点。因为要构造链表的拷贝,然后调整它们的指针并剥离拷贝的链表出来。从算法本身并不是很复杂,主要是这么多的步骤和指针操作很容易出错,而且很繁琐。需要一点一点的去分析。
相关推荐
leetcode中文版 LeetCode/Cpp 本人刷题记录在此,包含题意理解与算法思路,包含在Cpp文件内部注释,后续会持续更新。 有不懂的可以联系ji648513181,同时也欢迎志同道合O的朋友一起合作更新。 已更新剑指Offer答案...
python python_leetcode题解之138_Copy_List_with_Random_Pointer
javascript js_leetcode题解之138-copy-list-with-random-pointer.js
dna匹配 leetcode leetcode刷题--C++ 哈希表 Longest Substring ...Pointer 单链表 map Max Points on a Line 斜率 map, int> Fraction to Recurring Decimal map long long 正负号 Repeated DNA S
preorder-traversal链表reorder-list链表linked-list-cycle-ii链表linked-list-cycle动态规划word-break-ii动态规划word-break链表copy-list-with-random-pointer复杂度single-number-ii复杂度single-number动态规划
两两认识leetcode 使用随机指针复制链表 给出一个链表,使得每个节点都包含一个额外的随机指针,该指针可以指向链表中的任何节点或为空。 返回列表的深层副本。 链表在输入/输出中表示为 n 个节点的列表。 每个节点...
多线程 leetcode 前言 每天刷点leetcode,基于java语言实现。...Copy List with Random Pointer Building H2O Fizz Buzz Multithreaded hard Merge k Sorted Lists Reverse Nodes in k-Group Trapping Rain Water
24. Copy List with Random Pointer:复制带有随机指针的链表。 【二叉树】 25. Validate Binary Search Tree:验证二叉搜索树。 26. Maximum Depth of Binary Tree:计算二叉树的最大深度。 27. Minimum Depth of ...
- **Copy List with Random Pointer**:复制带有随机指针的链表。 8. **数学(Math)**: - **Reverse Integer**:反转一个整数。 9. **字符串(String)**: - **Add Binary**:将两个二进制数相加。 - **...
* [Linked List](https://github.com/kamyu104/LeetCode#linked-list) * [Stack](https://github.com/kamyu104/LeetCode#stack) * [Queue](https://github.com/kamyu104/LeetCode#queue) * [Heap]...
leetcode题库 pyshua Python 算法题练习 用法: python Judge.py library problem 例子: python Judge.py leetcode TwoSum 如何贡献: 收录题库 LeetCode (还有4题未录入, 分别为 LRU Cache, Copy List with Random ...
**1.10 Copy List with Random Pointer (138)** - **问题描述**:给定一个带有随机指针的链表,复制该链表。 - **解题思路**: - 遍历链表,在每个节点后面插入一个新节点。 - 再次遍历链表,更新新节点的随机...
7. **Copy List with Random Pointer**:这是一个涉及链表和深度复制的复杂问题。需要理解链表结构,并能创建一个新的链表,同时保留原链表的随机指针。 8. **Word Ladder II**:这是一个词链问题,涉及到广度优先...
- **2.2.10 Copy List with Random Pointer** - 复制一个带随机指针的链表。 - 实现思路:先复制链表中的每个节点,并将其插入到原节点后面,然后再处理随机指针。 #### 五、编写规范 - **单一文件编码**:由于...
- 有难度的链表题目则要求合并K个有序链表(Merge K Sorted Lists)、复制带有随机指针的链表(Copy List with Random Pointer)。 **二叉树(Binary Tree)** 二叉树是另一个重要的数据结构。LeetCode的题目涵盖了...
copy-list-with-random-pointer 复杂度 single-number 动态规划 candy 贪心 gas-station 动态规划 palindrome-partitioning-ii 动态规划 triangle 树 sum-root-to-leaf-numbers 动态规划 distinct-subsequences 递归...
- **复制带随机指针的链表(Copy List with Random Pointer)**: 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点,复制这个链表。 ##### 数学(Math) - **反转整数...
24. Copy List with Random Pointer:复制含有随机指针的链表。 四、二叉树 25. Validate Binary Search Tree:验证二叉搜索树的合法性。 26. Maximum Depth of Binary Tree:二叉树的最大深度。 27. Minimum Depth...