当前位置:   article > 正文

数据结构——单链表OJ题(下)

数据结构——单链表OJ题(下)

目录

一、链表的回文结构

思路一:数组法

(1)注意

(2)解题

思路二:反转链表法

(1) 注意

(2)解题

二、相交链表

(1)思路:快慢指针

(2)注意

(3)解题

 三、环形链表1

(1)思路:快慢指针

(2)解释

(3)注意

(4)解题

①快指针走两步

② 快指针走三步

四、环形链表2

(1)思路:快慢指针

(2)解释

(3)注意

(4)解题

五、随机链表的复制

(1)思路

(2)解题

六、写在最后 


一、链表的回文结构

思路一:数组法

将链表中结点的数据存储在数组中,创建两个指针分别从左右两边遍历并比较,如果左右对称则说明是回文结构。

(1)注意

①为什么要创建数组存储数据?因为链表的结点不是连续的,不能进行逆向访问;

②从左右两端进行遍历时,需满足left<right的条件;

③该方法只适用于链表长度小于等于900的条件下,因为数组的长度最大为900。

(2)解题

  1. typedef struct ListNode ListNode;
  2. bool chkPalindrome(ListNode* A)
  3. {
  4. //创建数组存储数据
  5. int arr[900] = {0};
  6. //数据的个数为i
  7. int i = 0;
  8. ListNode* pcur = A;
  9. //遍历链表,将数据存储在数组中
  10. while(pcur)
  11. {
  12. arr[i++] = pcur->val;
  13. pcur = pcur->next;
  14. }
  15. int left = 0;
  16. int right = i - 1;
  17. while(left < right)
  18. {
  19. //若有不相等,说明不是回文结构
  20. if(arr[left] != arr[right])
  21. {
  22. return false;
  23. }
  24. left++;
  25. right--;
  26. }
  27. //跳出循环,说明left和right指向的数据一直相等,即回文结构
  28. return true;
  29. }

思路二:反转链表法

使用快慢指针法找到原链表的中间结点,将中间结点之后的链表进行反转,最后将原链表的前半段与新链表存储的数据进行比较。

(1) 注意

①寻找中间结点和反转链表在上节已练习,可将它们封装成两个函数;

②由于原链表的长度大于新链表(反转后的),因此循环结束的判定条件是p2==NULL。

(2)解题

  1. class PalindromeList {
  2. //寻找中间结点的函数
  3. ListNode* midNode(ListNode* head)
  4. {
  5. ListNode* fast = head;
  6. ListNode* slow = head;
  7. while(fast && fast->next)
  8. {
  9. fast = fast->next->next;
  10. slow = slow->next;
  11. }
  12. return slow;
  13. }
  14. //将链表进行反转的函数
  15. ListNode* reverse(ListNode* head)
  16. {
  17. ListNode* n1, * n2 , *n3;
  18. n1 = NULL;
  19. n2 = head;
  20. n3 = head->next;
  21. while(n2)
  22. {
  23. n2->next = n1;
  24. n1 = n2;
  25. n2 = n3;
  26. if(n3)
  27. {
  28. n3 = n3->next;
  29. }
  30. }
  31. return n1;
  32. }
  33. public:
  34. bool chkPalindrome(ListNode* A)
  35. {
  36. //原链表的中间结点
  37. ListNode* mid = midNode(A);
  38. //将中间结点之后的链表进行反转
  39. ListNode* p2 = reverse(mid);
  40. ListNode* p1 = A;
  41. //遍历两个链表的值,比较是否相等
  42. while(p2)
  43. {
  44. //存在不相等的,说明不是回文结构
  45. if(p1->val != p2->val)
  46. {
  47. return false;
  48. }
  49. p1 = p1->next;
  50. p2 = p2->next;
  51. }
  52. //跳出循环,说明left和right指向的数据都相等,即为回文结构
  53. return true;
  54. }
  55. };

二、相交链表

(1)思路:快慢指针

首先找到两个链表长度的差值k,创建两个指针指向头结点,快指针先走k步,接着快慢指针一起走,若快慢指针指向同一个结点,则说明该结点为相交结点。

(2)注意

①计算差值时无法确定两个长度谁大谁小,可使用abs()绝对值函数;

②通过比较两个长度的大小来确定快指针指向哪个链表的头结点;

③判断快慢指针指向的结点是否相同,而非结点指向的数据!!

(3)解题

  1. typedef struct ListNode ListNode;
  2. struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
  3. {
  4. //计算两个链表的长度
  5. int len1 = 0;
  6. int len2 = 0;
  7. ListNode* pcur = headA;
  8. while(pcur)
  9. {
  10. pcur = pcur->next;
  11. len1++;
  12. }
  13. pcur = headB;
  14. while(pcur)
  15. {
  16. pcur = pcur->next;
  17. len2++;
  18. }
  19. //两个链表长度的差值k
  20. int k = abs(len1 - len2);
  21. //快指针指向长链表的头结点
  22. ListNode* fast = headA;
  23. ListNode* slow = headB;
  24. if(len1 < len2)
  25. {
  26. fast = headB;
  27. slow = headA;
  28. }
  29. //快指针先走k步
  30. while(k--)
  31. {
  32. fast = fast->next;
  33. }
  34. //比较两个指针指向的结点是否相同
  35. while(fast && slow)
  36. {
  37. if(fast == slow)
  38. {
  39. return fast;
  40. }
  41. fast = fast->next;
  42. slow = slow->next;
  43. }
  44. return NULL;
  45. }

 三、环形链表1

(1)思路:快慢指针

创建快慢指针并指向头结点,快指针一次经过两个结点,慢指针一次经过一个结点,若最终快指针与慢指针相遇,则说明该链表是环形链表。

(2)解释

①为什么在起点相同的情况下,快指针走两步,慢指针走一步,两者可以相遇,会不会遇不上?

假设此时fast进入环,slow走完入环前的距离准备入环,此时它们之间的距离为N。在接下来的追逐中,它们每追击(变换位置)一次,两者之间的距离缩小一步,直至相遇。(参考下图理解)

 

②如果慢指针一次走一步,快指针一次走3、4、5...n步,快慢指针还能再相遇吗?

可以!以快指针一次走3步为例:

slow入环后,slow和fast在环内进行追逐,假设两者之间的距离为N,那么在之后的过程中,每追逐一次两者之间的距离就缩小两步。

若N为偶数,最终距离可缩小为0,即相遇;若N为奇数,假设环的长度为C,会出现套圈情况(即slow走在fast前面),此时两者之间的长度为C-1。

此时需要讨论C-1是奇数还是偶数,若C-1为偶数,则可以相遇。若C-1为奇数,两者还会错过,继续套圈,那么fast和slow还会再相遇吗?

假设slow走完入环前的距离L,刚准备入环;fast在环里走了x圈,且此时fast和slow之间的距离为N,那么两者走过的长度分别为:slow : L     fast : L + xC + (C - N)。又因为fast的速度是slow的3倍,因此3L =  L + xC + (C - N),化简得:2L = (x + 1)C - N

上述条件中:N为奇数,C为偶数。因为:偶数= 偶数-偶数,偶数=奇数-奇数。那么(x+1)C为奇数时,会相遇。因此,如果慢指针一次走一步,快指针一次走3步,快慢指针还能再相遇。

同理可得其它步数的情况下也可相遇。

(3)注意

①循环结束的判定条件是fast && fast->next:若fast为空则说明不是环形链表;

②不能写成fast->next && fast,要先判断fast,因为如果fast为空,就不能取next;

③虽然证明了快指针不论走多少步都可以满足在带环链表中相遇,但是在编写代码的时候会有额外的步骤引入,涉及到快慢指针的算法题中通常习惯使用慢指针走一步、快指针走两步的方式。

(4)解题

①快指针走两步
  1. typedef struct ListNode ListNode;
  2. bool hasCycle(struct ListNode *head)
  3. {
  4. ListNode* fast = head;
  5. ListNode* slow = head;
  6. while(fast && fast->next)
  7. {
  8. fast = fast->next->next;
  9. slow = slow->next;
  10. if(fast == slow)
  11. {
  12. return true;
  13. }
  14. }
  15. return false;
  16. }
② 快指针走三步
  1. typedef struct ListNode ListNode;
  2. bool hasCycle(struct ListNode *head)
  3. {
  4. ListNode* fast = head;
  5. ListNode* slow = head;
  6. while(fast && fast->next)
  7. {
  8. slow = slow->next;
  9. //快指针一次走三步
  10. int n = 3;
  11. while(n--)
  12. {
  13. if(fast->next)
  14. {
  15. fast = fast->next;
  16. }
  17. else
  18. {
  19. return false;
  20. }
  21. }
  22. if(fast == slow)
  23. {
  24. return true;
  25. }
  26. }
  27. return false;
  28. }

四、环形链表2

(1)思路:快慢指针

创建两个指针,一个从链表的头结点开始遍历链表,另一个从快慢指针的(判断是否为环形链表的)相遇点开始绕环运行,两个指针都是一次走一步,最终会在入口点的位置相遇。

(2)解释

①为什么相遇点和头结点到入环结点的距离是相等的?

假设环的长度为R,入环之前的距离为L,入环节点到相遇点的距离为X,那么相遇点到入环结点之间的距离为R-X。

在判环时(此时在相遇点),快慢指针走的路程为:slow : L+X     fast :L+nR+X(其中n≥1)。

由于判环时,快指针一次走两步,慢指针一次走一步,则快指针的路程时慢指针的两倍。且慢指针入环之后,快指针一定会在一圈之内追上慢指针(因为慢指针入环之后,两者之间的距离最大为环的长度,而每追击一次,两者之间的距离缩短一步,因此在一圈之内,快指针一定能追上慢指针)。

那么:2*(L+X)= L+nR+X,化简为:L = nR - X,即L = (n -1)R+(R-X)。在极端情况下:假设n=1,则:L=R-X,即头结点到和相遇点到入环结点之间的距离相等。

(3)注意

①fast && fast->next位置的注意事项同上。

(4)解题

  1. typedef struct ListNode ListNode;
  2. struct ListNode *detectCycle(struct ListNode *head)
  3. {
  4. ListNode *fast = head;
  5. ListNode *slow = head;
  6. while(fast && fast->next)
  7. {
  8. //fast一次走两步,slow一次走一步
  9. fast = fast->next->next;
  10. slow = slow->next;
  11. if(fast == slow)
  12. {
  13. ListNode* pcur = head;
  14. //判断两者何时相遇
  15. while(pcur != slow)
  16. {
  17. pcur = pcur->next;
  18. slow = slow->next;
  19. }
  20. return slow;
  21. }
  22. }
  23. //链表不带环
  24. return NULL;
  25. }

五、随机链表的复制

 

(1)思路

在原链表的基础上继续复制链表,置random指针(copy->random = pcur->random->next),最后将复制链表和原链表断开,得到复制链表。

(2)解题

  1. typedef struct Node Node;
  2. Node* buyNode(int x) {
  3. Node* newnode = (Node*)malloc(sizeof(Node));
  4. newnode->val = x;
  5. newnode->next = newnode->random = NULL;
  6. return newnode;
  7. }
  8. void AddNode(Node* phead) {
  9. Node* pcur = phead;
  10. while (pcur)
  11. {
  12. // 存储pcur的下一个结点
  13. Node* Next = pcur->next;
  14. // 创建新结点,尾插到pcur
  15. Node* newnode = buyNode(pcur->val);
  16. pcur->next = newnode;
  17. newnode->next = Next;
  18. pcur = Next;
  19. }
  20. }
  21. struct Node* copyRandomList(struct Node* head) {
  22. if(head == NULL)
  23. {
  24. return NULL;
  25. }
  26. // 1.在原链表上复制结点
  27. AddNode(head);
  28. // 2.置random
  29. Node* pcur = head;
  30. while (pcur)
  31. {
  32. Node* copy = pcur->next;
  33. if (pcur->random != NULL)
  34. {
  35. copy->random = pcur->random->next;
  36. }
  37. pcur = copy->next;
  38. }
  39. // 3.断开链表
  40. pcur = head;
  41. Node *newHead, *newTail;
  42. newHead = newTail = pcur->next;
  43. while (pcur->next->next)
  44. {
  45. pcur = pcur->next->next;
  46. newTail->next = pcur->next;
  47. newTail = newTail->next;
  48. }
  49. return newHead;
  50. }

六、写在最后 

 链表既然包括单链表,就还有其他种类啦~

敬请期待“双向链表”~

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号