当前位置:   article > 正文

力扣LeetCode138. 复制带随机指针的链表 两种解法(C语言实现)_力扣138

力扣138

目录

题目链接

题目分析

 题目定位:

解题思路

解题思路1(粗暴但是复杂度高)

 解题思路2(巧妙并且复杂度低)


题目链接

138. 复制带随机指针的链表icon-default.png?t=N7T8https://leetcode-cn.com/problems/copy-list-with-random-pointer/

题目分析

 题目定位:

本题属于链表中较为综合的题目,考验做题者的思想以及用代码实现的能力,能够真正理解并做出此题需要对链表有相对熟练的掌握度。


话不多说,先来分析题目:

题目是想让我们构造一个链表的拷贝,也就是开辟一块一模一样的链表,但是本题中的链表,又和普通的单链表不一样,如图:

我们发现,链表中每一个节点中的结构体成员除了valnext之外,还有一个random,而random是指向链表中的任意一个节点,甚至可以是NULL。

因此要拷贝这个链表的难度就在于,如何使拷贝的链表中每一个节点的random指向其对应的节点呢?

解题思路

解题思路1(粗暴但是复杂度高)

第1步.先忽略原链表的random指针,对其进行复制,如图:

(1)定义两个struct Node*类型的指针copyhead和copytail来储存复制链表的头和尾,并初始化为NULL;并且定义一个struct Node*类型的指针cur指向head


(2)若原链表不为空,则此时cur指向第一个节点。用malloc开辟一块空间作为复制的第一个节点,并定义一个copy指针来保存,并将copy->val改为cur->val,并让copyhead和copytail指向copy节点


(3)让cur移向下一个节点。用malloc开辟一块空间作为复制的第二个节点,并让copy指向它,并将copy->val改为cur->val,先让copytail->next指向copy来连接两个节点,再让copytail指向copy节点(更新尾节点)。

像这样一直循环下去直到cur指向NULL,循环结束,最后再让copytail->next = NULL,此时便完成了第一步的复制操作

此时我们便可以根据图解来写代码

  1. struct Node* copyRandomList(struct Node* head) {
  2. //1.忽略random复制链表
  3. struct Node* cur = head;
  4. struct Node* copyhead = NULL;
  5. struct Node* copytail = NULL;
  6. while(cur)
  7. {
  8. struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
  9. copy->val = cur->val;
  10. if(copyhead == NULL)
  11. {
  12. copyhead = copy;
  13. copytail = copy;
  14. }
  15. else
  16. {
  17. //先连接
  18. copytail->next = copy;
  19. //再指向copy
  20. copytail = copy;
  21. }
  22. cur = cur->next;
  23. }
  24. copytail->next = NULL;
  25. }

第2步.处理拷贝链表的random指针

(1)查找原链表中的random指向的节点到底是第几个节点,并定义一个变量pos来记录下来

①如果原链表random指向NULL,则不需要找pos

②如果原链表的random指向链表中的节点,每次循环开始可以初始化一个新的指针newcur=head,再对newcur进行迭代,每当newcur向后走一次,pos加一,直到newcur == cur->random的时候,循环结束,举一个例子:


 (2)用一个循环来使拷贝链表中random指向其对应的节点

①如果原链表的random指向NULL,那么便很简单:拷贝的random直接置为NULL;

②如果原链表的random指向链表中的节点,每次循环开始可以初始化一个新的指针newcopy=copyhead,那么拷贝的random则可以通过一个循环来找到pos所对应的第几个节点,接着上面的例子:

根据图解我们可以写出代码:

  1. struct Node* copyRandomList(struct Node* head) {
  2. //1.忽略random复制链表
  3. struct Node* cur = head;
  4. struct Node* copyhead = NULL;
  5. struct Node* copytail = NULL;
  6. while (cur)
  7. {
  8. struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
  9. copy->val = cur->val;
  10. if (copyhead == NULL)
  11. {
  12. copyhead = copy;
  13. copytail = copy;
  14. }
  15. else
  16. {
  17. //先连接
  18. copytail->next = copy;
  19. //再指向copy
  20. copytail = copy;
  21. }
  22. cur = cur->next;
  23. copytail->next = NULL;
  24. }
  25. //2.处理拷贝链表的random指针
  26. cur = head;
  27. struct Node* copy = copyhead;
  28. while (cur)
  29. {
  30. int pos = 0;
  31. if (cur->random == NULL)
  32. {
  33. copy->random = NULL;
  34. }
  35. //找到cur的random指向对应的第几个节点
  36. else
  37. {
  38. //让newcur和newcopy重新指向第一个节点
  39. struct Node* newcur = head;
  40. struct Node* newcopy = copyhead;
  41. while (newcur != cur->random)
  42. {
  43. newcur = newcur->next;
  44. pos++;
  45. }
  46. //使copy的random指向对应的第pos个节点
  47. while (pos--)
  48. {
  49. newcopy = newcopy->next;
  50. }
  51. copy->random = newcopy;
  52. }
  53. //cur和copy向后走
  54. cur = cur->next;
  55. copy = copy->next;
  56. }
  57. return copyhead;
  58. }

 题解一总结:优点是这种思路比较容易想到并且理解;缺点是由于找到原链表找到random指向的节点的pos的过程,每个节点的复杂度为O(n),又有n个节点,因此这种解法的时间复杂度为O(n²),并不是一个优秀的解法,接下来将为大家讲另一种优秀的解法。


 解题思路2(巧妙并且复杂度低)

第1步 把拷贝节点连接再原节点的后面

用这种方式处理是为了方便找到拷贝链表的random应该指向第几个节点,random指向的这个节点的下一个节点就是拷贝链表要找的random节点

根据图解我们可以写出代码:

  1. //1.链接链表
  2. struct Node* cur = head;
  3. while(cur)
  4. {
  5. //提前储存原链表下一个节点的地址
  6. struct Node* next = cur->next;
  7. //为原链表的拷贝开辟空间
  8. struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
  9. copy->val = cur->val;
  10. //链接原链表和拷贝节点
  11. cur->next = copy;
  12. copy->next = next;
  13. //迭代往下走
  14. cur = next;
  15. }

 第2步 拷贝原链表的random节点

 (1)若原链表的节点的random指向NULL,拷贝节点的random也指向NULL


(2)若原链表的节点random指向原链表的其中一个节点,那么random指向的这个节点的下一个节点就是拷贝链表要找的random,以原链表二个节点为例:

(3)实现后题解如下

根据图解我们可以写出代码:

  1. //2.拷贝原链表的random节点
  2. cur = head;
  3. while(cur)
  4. {
  5. struct Node* copy = cur->next;
  6. if(cur->random == NULL)
  7. {
  8. copy->random = NULL;
  9. }
  10. else
  11. {
  12. copy->random = cur->random->next;
  13. }
  14. cur = copy->next;
  15. }

第3步 将拷贝节点与原链表分开

(1)用cur和copy指针来分别使原链表和拷贝链表的节点向后迭代,由于copy随着cur向后迭代,一直跟在cur的后面

因此循环外初始化cur = head,循环内初始化copy = cur->next;

 (2)为原链表定义一个next来连接链表;为拷贝链表定义一个copyhead和copytail来返回和连接链表,当原链表第一个节点不为空,才能保证copy的第一个节点不为空,因此copyhead和copytail先置空

循环外初始化copytail = copyhead = NULL,循环内初始化next = copy->next, 

(3)当cur指向第一个节点时,进入循环,此时copyhead和copytail为空,使其指向拷贝链表的第一个节点,再使cur->next = next,将cur和next连接起来,并使cur=next往后走,

 若cur不为空,再之后就是让copy=cur->next,next = copy->next

  捋清楚结构就可以写出循环体框架:

  1. cur = head;
  2. struct Node* copyhead = NULL;
  3. struct Node* copytail = NULL;
  4. while(cur)
  5. {
  6. struct Node* copy = cur->next;
  7. struct Node* next = copy->next;
  8. if(copyhead == NULL)
  9. {
  10. copyhead = copytail = copy;
  11. }
  12. cur->next = next;
  13. cur = next;
  14. }

(4)当cur指向第二个节点时,copyhead和copytail此时不为空,copy也指向拷贝的第二个节点,因此用copytail->next = copy连接两节点,再使copytail = copy使其向后迭代

捋清关系我们便可以补全循环体

  1. //3.将拷贝节点与原链表分开
  2. cur = head;
  3. struct Node* copyhead = NULL;
  4. struct Node* copytail = NULL;
  5. while(cur)
  6. {
  7. struct Node* copy = cur->next;
  8. struct Node* next = copy->next;
  9. if(copyhead == NULL)
  10. {
  11. copyhead = copytail = copy;
  12. }
  13. else
  14. {
  15. copytail->next = copy;
  16. copytail = copy;
  17. }
  18. cur->next = next;
  19. cur = next;
  20. }

完整代码: 

  1. struct Node* copyRandomList(struct Node* head) {
  2. //1.链接链表
  3. struct Node* cur = head;
  4. while(cur)
  5. {
  6. //提前储存原链表下一个节点的地址
  7. struct Node* next = cur->next;
  8. //为原链表的拷贝开辟空间
  9. struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
  10. copy->val = cur->val;
  11. //链接原链表和拷贝节点
  12. cur->next = copy;
  13. copy->next = next;
  14. //迭代往下走
  15. cur = next;
  16. }
  17. //2.拷贝原链表的random节点
  18. cur = head;
  19. while(cur)
  20. {
  21. struct Node* copy = cur->next;
  22. if(cur->random == NULL)
  23. {
  24. copy->random = NULL;
  25. }
  26. else
  27. {
  28. copy->random = cur->random->next;
  29. }
  30. cur = copy->next;
  31. }
  32. //3.将拷贝节点与原链表分开
  33. cur = head;
  34. struct Node* copyhead = NULL;
  35. struct Node* copytail = NULL;
  36. while(cur)
  37. {
  38. struct Node* copy = cur->next;
  39. struct Node* next = copy->next;
  40. if(copyhead == NULL)
  41. {
  42. copyhead = copytail = copy;
  43. }
  44. else
  45. {
  46. copytail->next = copy;
  47. copytail = copy;
  48. }
  49. cur->next = next;
  50. cur = next;
  51. }
  52. return copyhead;
  53. }

 题解二总结:由于第二种解题思路用巧妙的连接方式,使拷贝链表的random不需要通过对原链表的每一个节点遍历也能找到,大大降低了时间复杂度,这种解法的时间复杂度为O(n)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/620661
推荐阅读
相关标签
  

闽ICP备14008679号