当前位置:   article > 正文

环形链表 I 环形链表 II 随机链表的复制

环形链表 I 环形链表 II 随机链表的复制

环形链表 I

判断一个链表是否带环 

 思路一

快慢指针

创建一块一慢两个指针,去遍历链表,如果快指针走到了最后一个节点或走到了NULL,则此链表不带环。

如果带环,在进入环后,快慢指针会展开追逐,当快指针追到慢指针时,则此链表带环

代码:

 这里我们让快指针一次走两步,慢指针一次走一步

  1. bool hasCycle(struct ListNode *head) {
  2. struct ListNode* fast = head,*slow = head;
  3. while(fast && fast->next)
  4. {
  5. slow = slow -> next;
  6. fast = fast->next->next;
  7. if(fast == slow)
  8. {
  9. return true;
  10. }
  11. }
  12. return false;
  13. }

思路一的问题

问题一 :为什么一定会相遇?会不会出现错过的情况?永远追不上

想解释这个问题比较简单,我们假设有一带环链表List,和快指针 fast 慢指针 slow

此图为假设 slow 指针刚进入环时,与fast指针距离为N

因为fast指针的速度是slow指针速度的两倍,所以slow每走一步,fast都会追上slow一步

它们之间的距离也会不断的缩减:N  N -1 N -1 .....3 2 1 0,当N为0时,fast就追上slow了,并不会出现错过的情况

问题二 :slow一次走一步,fast一次走 3步 4步 5步 n步 可以吗 ?

这里我们只推导 slow一次走一步,fast 一次走3步的过程,其余推导过程类似

此图为假设 slow 指针刚进入环时,与fast指针距离为N

情况一:当N是偶数时 —— 直接追上

fast 追击 slow 情况:N  N - 2  N - 3 ......4  2  0 刚好可以追上

情况二 :当 N 是奇数时 —— 展开新一轮的追击

结论:当 N 是奇数 且 C 为偶数时,永远也追不上

验证结论

经过推理我们得出:当 N 是奇数 且 C 为偶数时,fast 永远也追不上 slow,接下来我们尝试建立N与C的联系,来验证它是否正确

 slow 刚进入圆环时,研究slow与fast走过的路程有:

slow 走的距离 :L

fast走的距离 :L + XC + C - N(X为圈数)

fast走的距离为 slow 的3倍 :3L = L + XC + C - N

化简该等式有:2L = (X + 1)C -N

将我们刚刚得出的结论(当 N 是奇数 且 C 为偶数时,永远也追不上)带入该等式,我们发现:

所以结论(当 N 是奇数 且 C 为偶数时,永远也追不上),不成立

结论

当 slow 走一步 fast 走三步时 ,fast一定追的上 slow不存在永远追不上的情况!

环形链表 || 

找环的入口点 

  

 思路一

双指针

先找到 fast指针 与 slow指针 相遇的节点meet ,如找不到返回NULL

找到meet节点后:让一个指针从头开始走,一个指针从meet开始走(两个指针走的速度一样),当两个指针相遇时,这个节点就是入口点

代码:

  1. struct ListNode * hasCycle(struct ListNode *head) {
  2. struct ListNode* fast = head,*slow = head;
  3. while(fast && fast->next)
  4. {
  5. slow = slow -> next;
  6. fast = fast->next->next;
  7. if(fast == slow)
  8. {
  9. return fast;
  10. }
  11. }
  12. return NULL;
  13. }
  14. struct ListNode *detectCycle(struct ListNode *head) {
  15. //找到meet节点
  16. struct ListNode* meet = hasCycle(head);
  17. if(meet == NULL)
  18. return NULL;
  19. //让慢指针重新开始走,快指针从meet位置开始走,当它们再次相遇时,就是环的入口点
  20. struct ListNode* cur_in = meet;//一个指针从meet开始走
  21. struct ListNode* cur_out = head;//一个指着从链表头部开始走
  22. while(true)
  23. {
  24. if(cur_in == cur_out)
  25. {
  26. return cur_in;
  27. }
  28. cur_in = cur_in->next;
  29. cur_out = cur_out->next;
  30. }
  31. }

思路一的问题

为什么两个指针会在环的入口点相遇 

研究快慢指针相遇走的路程时有:

 slow走过的路程:L + N

fast走过的路程:L + XC + N (X大于等于1,且X为整数 )

fast走过的路程是slow的2倍:2(L + N) = L + XC + N

化简:2(L + N) = L + XC + N         =        L = XC - C      =      L =  (X-1) + C - N

这个公式就是为什么两个相同速度的指针会在环的入口点相遇了:L =  (X-1) + C - N

 

 

 

由于L  = C - N所以它们才会在环的入口点相遇

思路二 

在相遇点把环给解开,这样问题就变成了找两个链表相遇的问题

代码:

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * struct ListNode *next;
  6. * };
  7. */
  8. struct ListNode * hasCycle(struct ListNode *head) {
  9. struct ListNode* fast = head,*slow = head;
  10. while(fast && fast->next)
  11. {
  12. slow = slow -> next;
  13. fast = fast->next->next;
  14. if(fast == slow)
  15. {
  16. return fast;
  17. }
  18. }
  19. return NULL;
  20. }
  21. struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
  22. struct ListNode * curA;
  23. struct ListNode * curB;
  24. struct ListNode * cur;
  25. int countA = 0;
  26. int countB = 0;
  27. //统计A链表总节点
  28. cur = headA;
  29. while(cur)
  30. {
  31. cur = cur->next;
  32. countA++;
  33. }
  34. //统计B链表总结点
  35. cur = headB;
  36. while(cur)
  37. {
  38. cur = cur->next;
  39. countB++;
  40. }
  41. //让链表长的指针走到与链表短的指针同位置
  42. int gap = abs(countA - countB);
  43. struct ListNode * longList = headA,*shortList = headB;
  44. if(countB > countA)//修正长短链表
  45. {
  46. longList = headB;
  47. shortList = headA;
  48. }
  49. while(gap--)
  50. {
  51. longList = longList -> next;
  52. }
  53. //开始查找有没有相交的节点
  54. while(longList) //随意哪一个走到NULL就结束
  55. {
  56. if(longList == shortList) //节点地址相同说明这是相交的第一个节点
  57. {
  58. return longList;
  59. }
  60. longList = longList -> next;
  61. shortList = shortList -> next;
  62. }
  63. //一直没有相交的节点就返回NULL
  64. return NULL;
  65. }
  66. struct ListNode *detectCycle(struct ListNode *head) {
  67. //找到相遇点
  68. struct ListNode* meet = hasCycle(head);
  69. if(meet == NULL)
  70. {
  71. return NULL;
  72. }
  73. //解开环
  74. struct ListNode* newhead = meet->next;
  75. meet->next = NULL;
  76. //找两条链表相交点
  77. meet = getIntersectionNode(newhead,head);
  78. return meet;
  79. }

随机链表的复制

 

复制一个链表,难题在于链表中的random指针怎么去复制

由于random指针指向的是自身链表中的节点

你复制的链表节点的各自random指针也要和源链表一样,指向各自的节点 

思路一

把复制链表依次插入到源链表各个节点中

这样,复制节点的random指针就相当于 == 被复制节点random指针指向的next节点

代码: 

  1. struct Node* copyRandomList(struct Node* head) {
  2. if(head == NULL)
  3. {
  4. return NULL;
  5. }
  6. struct Node* cur = head;
  7. //将拷贝链表的节点插缝源链表中
  8. while(cur)
  9. {
  10. struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
  11. copy -> val = cur -> val;
  12. copy -> next = cur -> next;//copy节点插入cur节点与cur下一个节点之间
  13. cur -> next = copy;
  14. cur = copy -> next;//cur走向源链表的下一个节点
  15. }
  16. //使用我们准备好的链表,它可以帮助我们建立random的联系
  17. cur = head;
  18. struct Node* copy;
  19. while(cur)//当走到NULL时停止,其实这个是拷贝链表最后一个节点的nxte指向NULL
  20. {
  21. copy = cur -> next;//创建copy指向每个源链表后的拷贝节点
  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. //解开链表
  33. cur = head;//cur遍历源链表
  34. struct Node* source_next;//源链表的下一个节点
  35. struct Node* copy_sentry = (struct Node*)malloc(sizeof(struct Node));//哨兵节点
  36. struct Node* copy_tail;//拷贝链表尾节点
  37. copy_tail = copy_sentry;
  38. while(cur)
  39. {
  40. copy = cur -> next;
  41. source_next = copy->next;
  42. //拷贝链表的尾插
  43. copy_tail->next = copy;
  44. copy_tail = copy_tail -> next;
  45. //回复源链表
  46. cur->next = source_next;
  47. //cur和source_next在源链表上各走一步
  48. cur = source_next;
  49. }
  50. return copy_sentry->next;
  51. }

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

闽ICP备14008679号