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

解题思路1:哈希表

示例:
image.png
示例中,黑色的箭头表示为 next 指针,红色的箭头表示为 random 指针

我们可以使用哈希表,哈希表的键存储原链表的节点,哈希表的值存储复制后的节点。

链表中的原节点记作 node,复制后的节点记作 node’

两者之间具有如下关系:

  • node'.next = map.get(node.next)
  • node'.random = map.get(node.random)

代码

  1. /*
  2. // Definition for a Node.
  3. class Node {
  4. int val;
  5. Node next;
  6. Node random;
  7. public Node(int val) {
  8. this.val = val;
  9. this.next = null;
  10. this.random = null;
  11. }
  12. }
  13. */
  14. class Solution {
  15. public Node copyRandomList(Node head) {
  16. Map<Node,Node> map = new HashMap<>();
  17. Node cur = head;
  18. while(cur != null){
  19. map.put(cur,new Node(cur.val));
  20. cur = cur.next;
  21. }
  22. cur = head;
  23. while(cur != null){
  24. map.get(cur).next = map.get(cur.next);
  25. map.get(cur).random = map.get(cur.random);
  26. cur = cur.next;
  27. }
  28. return map.get(head);
  29. }
  30. }

复杂度分析

  • 时间复杂度:O(N)

使用哈希表,我们需要两轮遍历链表,所以时间复杂度为: O(N)

  • 空间复杂度:O(N)

我们使用哈希表存储链表的节点,占用了 O(N) 的额外空间

解题思路2:拼接与拆分

第二种思路为拼接与拆分链表,可以分为三个步骤:

  1. 拼接链表,将复制后的节点放到原节点之后,使用 next 指针相关联
  2. 构建复制节点的 random 指针
  3. 将复制后的链表从原链表中拆分出去

拼接链表

我们首先不考虑 random 指针,将链表拼接至下图所示:

image.png

蓝色的节点为复制后的节点,我们将每一个复制后的节点都放在原节点之后,使用 next 指针相连接

构建 random 指针

然后,我们构建复制后节点之间 random 指针的关联

原节点记作 node,复制后的节点记作 node’,二者之间满足:

  • node.next = node'
  • node'.random = node.random.next

构建好 random 指针的关联后的链表如下图所示:

image.png

拆分链表

第三步,我们需要将复制后的链表从原链表中拆分出去

只需要将复制后的节点的 next 指针指向复制后的节点即可。同时我们为了不破坏原链表,还需要将原链表的节点的 next 指针指向原链表的节点。

image.png

代码

  1. /*
  2. // Definition for a Node.
  3. class Node {
  4. int val;
  5. Node next;
  6. Node random;
  7. public Node(int val) {
  8. this.val = val;
  9. this.next = null;
  10. this.random = null;
  11. }
  12. }
  13. */
  14. class Solution {
  15. public Node copyRandomList(Node head) {
  16. if(head == null){
  17. return null;
  18. }
  19. Node cur = head;
  20. while(cur != null){
  21. Node node = new Node(cur.val);
  22. node.next = cur.next;
  23. cur.next = node;
  24. cur = node.next;
  25. }
  26. cur = head;
  27. while(cur != null){
  28. cur.next.random = cur.random == null ? null :cur.random.next;
  29. cur = cur.next.next;
  30. }
  31. cur = head;
  32. Node res = head.next;
  33. Node cur2 = head.next;
  34. while(cur2.next != null){
  35. cur.next = cur.next.next;
  36. cur2.next = cur2.next.next;
  37. cur = cur.next;
  38. cur2 = cur2.next;
  39. }
  40. // 不要忘记处理原链表的尾节点
  41. cur.next = null;
  42. return res;
  43. }
  44. }

复杂度分析

  • 时间复杂度: O(N)

我们总共三轮遍历链表,使用 O(N) 的时间

  • 空间复杂度: O(1)

我们仅使用了有限几个变量,使用了常数大小的额外空间,所以空间复杂度为 O(1)