当前位置:   article > 正文

【LeetCode】链表oj专题

【LeetCode】链表oj专题

前言

经过前面的学习,咋们已经学完了链表相关知识,这时候不妨来几道链表算法题来巩固一下吧!

如果有不懂的可翻阅之前文章哦!

个人主页小八哥向前冲~-CSDN博客

数据结构专栏:数据结构【c语言版】_小八哥向前冲~的博客-CSDN博客

c语言专栏:c语言_小八哥向前冲~的博客-CSDN博客

这里的题目都是非常经典的好题目哦!能让我们对链表的理解更深一步!

当然,有些题目非常有意思哦!哈哈哈哈!我们一起来看看!

目录

前言

1.反转链表

2.删除链表节点

3.寻找中间节点

4.环形链表的约瑟夫问题

5.合并有序链表

6.输出倒数k节点

7.链表的回文结构

8.判断相交链表

9.环形链表

10.环形链表2

11.随机链表的复制


1.反转链表

题目

给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。

题目详情:LCR 024. 反转链表 - 力扣(LeetCode)

思路

看到这道题是不是觉得稳了?是不是觉得很简单?

我们只需要一个一个节点遍历链表,将一个个节点按顺序拿下来头插不就行了!

上图理解:

代码

  1. typedef struct ListNode ListNode;
  2. struct ListNode* reverseList(struct ListNode* head){
  3. ListNode*newhead=NULL,*cur=head;
  4. //头插
  5. while(cur)
  6. {
  7. ListNode*next=cur->next;
  8. cur->next=newhead;
  9. newhead=cur;
  10. cur=next;
  11. }
  12. return newhead;
  13. }

思路2

创建哨兵位节点,将原链表遍历,把每个链表一个一个尾插到哨兵位的后面

是不是也很简单?上图理解一下:

代码

  1. typedef struct ListNode ListNode;
  2. struct ListNode* reverseList(struct ListNode* head){
  3. ListNode*cur=head;
  4. //创建哨兵位节点
  5. ListNode*newhead=(ListNode*)malloc(sizeof(ListNode));
  6. newhead->next=NULL;
  7. while(cur)
  8. {
  9. ListNode*next=cur->next;
  10. cur->next=newhead->next;
  11. newhead->next=cur;
  12. cur=next;
  13. }
  14. return newhead->next;
  15. }

2.删除链表节点

题目

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

详情:LCR 136. 删除链表的节点 - 力扣(LeetCode)

思路

创建一个哨兵位,遍历原链表,将不是指定节点全部拿下来尾插,得到新的链表!

上图解释一下:

代码

  1. typedef struct ListNode ListNode;
  2. struct ListNode* deleteNode(struct ListNode* head, int val){
  3. ListNode*newhead=(ListNode*)malloc(sizeof(ListNode));
  4. ListNode*tail=newhead;
  5. ListNode*cur=head;
  6. while(cur)
  7. {
  8. if(cur->val!=val)
  9. {
  10. tail->next=cur;
  11. tail=cur;
  12. }
  13. cur=cur->next;
  14. }
  15. tail->next=NULL;
  16. return newhead->next;
  17. }

3.寻找中间节点

题目

给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

详情:876. 链表的中间结点 - 力扣(LeetCode)

思路

定义两个指针(快慢指针),快指针走两步,慢指针走一步,最终慢指针便是中间节点!但是需要分奇数链表和偶数链表。

上图:

代码

  1. typedef struct ListNode ListNode;
  2. struct ListNode* middleNode(struct ListNode* head) {
  3. ListNode*slow,*fast;
  4. //快慢指针,慢指针走一步,快指针走两步
  5. slow=fast=head;
  6. while(fast&&fast->next)//两种情况,一种节点为奇数,一种节点为偶数
  7. {
  8. slow=slow->next;
  9. fast=fast->next->next;
  10. }
  11. return slow;
  12. }

4.环形链表的约瑟夫问题

题目

详情:环形链表的约瑟夫问题_牛客题霸_牛客网 (nowcoder.com)

思路

1.创建链表节点

2.将节点连成环

3.开始纱人

图:

代码

  1. typedef struct ListNode ListNode;
  2. //创建链表节点函数
  3. ListNode*Buynode(int x)
  4. {
  5. ListNode*node=(ListNode*)malloc(sizeof(ListNode));
  6. node->val=x;
  7. node->next=NULL;
  8. return node;
  9. }
  10. //将节点连成环,创建循环链表
  11. ListNode* NodeCircle(int n)
  12. {
  13. //先创建第一个节点
  14. ListNode*phead=Buynode(1);
  15. ListNode*ptail=phead;
  16. for(int i=2;i<=n;i++)
  17. {
  18. ptail->next=Buynode(i);
  19. ptail=ptail->next;
  20. }
  21. ptail->next=phead;//将节点连起来
  22. return ptail;
  23. }
  24. int ysf(int n, int m ) {
  25. // write code here
  26. ListNode*prev=NodeCircle(n);
  27. ListNode*pcur=prev->next;
  28. int count=1;
  29. while(pcur->next!=pcur)//最后只剩一个人,它的下一个节点是它自己
  30. {
  31. if(count==m)//数到m就自杀
  32. {
  33. prev->next=pcur->next;
  34. free(pcur);
  35. pcur=prev->next;
  36. count=1;//当杀完后,置为1,重新数数
  37. }
  38. else
  39. {
  40. //没数到m就开始数(挪prev和pcur位置)
  41. prev=pcur;
  42. pcur=pcur->next;
  43. count++;
  44. }
  45. }
  46. return pcur->val;
  47. }

5.合并有序链表

题目

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

详情:21. 合并两个有序链表 - 力扣(LeetCode)

思路

同时遍历两个链表和创建一个哨兵位,每个节点每个节点进行比较,小的那个节点尾插到带头哨兵后面,注意当其中有一个为空时,则另一个还未尾插完!

上图理解:

代码

  1. typedef struct ListNode ListNode;
  2. struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
  3. ListNode*l1=list1;
  4. ListNode*l2=list2;
  5. ListNode*newhead,*newtail;
  6. newhead=newtail=(ListNode*)malloc(sizeof(ListNode));//哨兵节点
  7. while(l1&&l2)
  8. {
  9. if(l1->val<l2->val)
  10. {
  11. //将l1拿下来尾插
  12. newtail->next=l1;
  13. newtail=newtail->next;
  14. l1=l1->next;
  15. }
  16. else
  17. {
  18. //将l2拿下来尾插
  19. newtail->next=l2;
  20. newtail=newtail->next;
  21. l2=l2->next;
  22. }
  23. }
  24. //跳出循环有两种情况,要么l1没尾插完,要么l2没尾插完
  25. if(l1==NULL)
  26. {
  27. newtail->next=l2;
  28. //newtail=newtail->next;
  29. }
  30. if(l2==NULL)
  31. {
  32. newtail->next=l1;
  33. //newtail=newtail->next;
  34. }
  35. return newhead->next;
  36. }

6.输出倒数k节点

题目

实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。

思路

定义快慢指针,但这里的快慢指针并不是和寻找中间节点那个快慢指针一样!听我道来。

这里要找倒数第几个节点,快指针就先走几个节点!随后一起俩指针一起走。当快指针走到空时,慢指针指向的就是倒数k节点!

图:

代码

  1. typedef struct ListNode ListNode;
  2. int kthToLast(struct ListNode* head, int k){
  3. //定义快慢指针,快指针开始就比满指针快k步,随后一直一步一步走
  4. ListNode*slow=head,*fast=head;
  5. while(k--)
  6. {
  7. fast=fast->next;
  8. }
  9. //随后一起走,当快指针走到空,慢指针就是那个节点
  10. while(fast)
  11. {
  12. fast=fast->next;
  13. slow=slow->next;
  14. }
  15. return slow->val;
  16. }

7.链表的回文结构

题目

给定一个链表的 头节点 head ,请判断其是否为回文链表。

如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的。

详情:LCR 027. 回文链表 - 力扣(LeetCode)

注意:这题目很经典很重要!需要掌握!

思路

先找到中间节点,再反转中间节点之后的节点!定义俩个指针一个头指针,一个中间节点指针。

俩指针同时遍历比较,途中但凡没有相同的就不是回文结构!

是不是觉得很简单?没错,其实主要就掌握链表的反转和寻找链表的中间节点就行!

上图:

代码

  1. typedef struct ListNode ListNode;
  2. //寻找中间节点函数
  3. ListNode*MidNode(ListNode*head)
  4. {
  5. //快慢指针
  6. ListNode*slow=head,*fast=head;
  7. //慢指针走一步,快指针走两步
  8. while(fast&&fast->next)
  9. {
  10. slow=slow->next;
  11. fast=fast->next->next;
  12. }
  13. //慢指针就是中间节点
  14. return slow;
  15. }
  16. //翻转节点函数
  17. ListNode*reverse(ListNode*head)
  18. {
  19. ListNode*newhead=NULL,*cur=head;
  20. while(cur)
  21. {
  22. ListNode*next=cur->next;
  23. cur->next=newhead;
  24. newhead=cur;
  25. cur=next;
  26. }
  27. return newhead;
  28. }
  29. bool isPalindrome(struct ListNode* head){
  30. ListNode*mid=MidNode(head);
  31. ListNode*head1=head;
  32. ListNode*head2=reverse(mid);
  33. while(head2&&head1)
  34. {
  35. if(head1->val!=head2->val)
  36. {
  37. return false;
  38. }
  39. head1=head1->next;
  40. head2=head2->next;
  41. }
  42. return true;
  43. }

8.判断相交链表

题目

给定两个单链表的头节点 headA 和 headB ,请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

详情:LCR 023. 相交链表 - 力扣(LeetCode)

思路

分别遍历俩链表且计算俩链表的长度。如果最后一个节点(地址)相同(注意这里不要用值比较,可能不是一个节点只是值相同而已!),计算出长的链表,先走相差步(俩链表相减的绝对值)

最终同时遍历,一直比较,直到比较到俩链表的相同节点,就找到了相交节点!

代码

  1. typedef struct ListNode ListNode;
  2. struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
  3. ListNode*curA=headA,*curB=headB;
  4. int lenA=0,lenB=0;
  5. while(curA->next)
  6. {
  7. lenA++;
  8. curA=curA->next;
  9. }
  10. while(curB->next)
  11. {
  12. lenB++;
  13. curB=curB->next;
  14. }
  15. if(curA!=curB)
  16. {
  17. return NULL;
  18. }
  19. int tmp=abs(lenA-lenB);
  20. ListNode*shortList=headA,*longList=headB;
  21. if(lenA>lenB)
  22. {
  23. shortList=headB;
  24. longList=headA;
  25. }
  26. while(tmp--)
  27. {
  28. longList=longList->next;
  29. }
  30. while(shortList!=longList)
  31. {
  32. shortList=shortList->next;
  33. longList=longList->next;
  34. }
  35. return shortList;
  36. }

9.环形链表

题目

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

详情:141. 环形链表 - 力扣(LeetCode)

思路

定义快慢指针,慢指针走一步,快指针走两步,如果没有环就是寻找中间节点的情景,如果有环,因为有速度差又是闭合的环状,最终会追到!

代码

  1. bool hasCycle(struct ListNode *head) {
  2. struct ListNode*slow=head,*fast=head;
  3. //如果没有环,分奇数偶数情况,奇数fast->next为空,偶数fast为空
  4. while(fast&&fast->next)
  5. {
  6. slow=slow->next;
  7. fast=fast->next->next;
  8. if(slow==fast)
  9. {
  10. return true;
  11. }
  12. }
  13. return false;
  14. }

拓展

俩个问题:

  1. 一定会追到吗?有没有可能会错过?请证明一下!
  2. 如果快指针走3步呢?还追得上吗?如果慢指针走俩步呢?又追不追得上?

第一个问题解答

一定会追上!慢指针走一步,快指针走俩步一定会追上!

慢指针走一步,快指针走俩步,它们的相对速度其实就是一步!什么意思呢?它们之间的距离会一步一步的减少,就是因为是一步一步的减少才不会错过!

第二个问题解答

如果快指针走三步,慢指针走一步的话,不一定能追上!因为它们的相对速度是2步,可能会错过!

如果当慢指针进环时,快指针和慢指针的距离如果是偶数的话,就能追上!

如果当慢指针进环时,快指针和慢指针的距离如果是奇数的话,就错过了!那么开始第二圈的追逐!当开始追的时候,它们的距离如果是偶数,第二圈就追上了!如果是奇数,第二圈追不上,那么之后永远追不上!

上图解释一下第二个问题:

10.环形链表2

题目

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

详情:142. 环形链表 II - 力扣(LeetCode)

思路1

仍然定义快慢指针,快指针走俩步,慢指针走一步,最终会追到一起!

再将追到一起的节点和头节点一起遍历,当俩指针相等时则就是环链表的入口节点。

你是不是有疑问?为什么俩指针相等时就是环链表的入口节点?

我们上图解释一下:

代码

  1. struct ListNode *detectCycle(struct ListNode *head) {
  2. struct ListNode*slow=head,*fast=head;
  3. while(fast&&fast->next)
  4. {
  5. slow=slow->next;
  6. fast=fast->next->next;
  7. if(slow==fast)
  8. {
  9. struct ListNode*meet=slow;
  10. struct ListNode*cur=head;
  11. while(meet!=cur)
  12. {
  13. meet=meet->next;
  14. cur=cur->next;
  15. }
  16. return cur;
  17. }
  18. }
  19. return NULL;
  20. }

思路2

还是定义快慢指针,慢指针走一步,快指针走俩步,最终终会追上!

将相逢节点断开,然后就变成了俩个链表,问题变成了在相交链表中寻找相交节点!

上图:

代码

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * struct ListNode *next;
  6. * };
  7. */
  8. struct ListNode*Intersect(struct ListNode*head1,struct ListNode*head2)
  9. {
  10. struct ListNode*cur1=head1,*cur2=head2;
  11. int len1=0,len2=0;
  12. while(cur1)
  13. {
  14. cur1=cur1->next;
  15. len1++;
  16. }
  17. while(cur2)
  18. {
  19. cur2=cur2->next;
  20. len2++;
  21. }
  22. int tmp=abs(len1-len2);
  23. struct ListNode*shortList=head1,*longList=head2;
  24. if(len1>len2)
  25. {
  26. longList=head1;
  27. shortList=head2;
  28. }
  29. //长的先走相差步
  30. while(tmp--)
  31. {
  32. longList=longList->next;
  33. }
  34. while(longList!=shortList)
  35. {
  36. longList=longList->next;
  37. shortList=shortList->next;
  38. }
  39. return longList;
  40. }
  41. struct ListNode *detectCycle(struct ListNode *head) {
  42. struct ListNode*slow=head,*fast=head;
  43. while(fast&&fast->next)
  44. {
  45. slow=slow->next;
  46. fast=fast->next->next;
  47. if(slow==fast)
  48. {
  49. //将环拆开,变成寻找两链表的中间节点
  50. fast=fast->next;
  51. slow->next=NULL;
  52. return Intersect(fast,head);//寻找两链表的中间节点
  53. }
  54. }
  55. return NULL;
  56. }

11.随机链表的复制

题目

详情:138. 随机链表的复制 - 力扣(LeetCode)

思路

1.遍历原链表,一个一个创建新节点连接到原节点的后面!

2.再遍历原链表,按照原链表randrom的连接来连接新节点!

3.再遍历,从原链表上面拆解新链表下来!(尾插)

图文理解:

代码

  1. struct Node* copyRandomList(struct Node* head) {
  2. struct Node*cur=head;
  3. while(cur)
  4. {
  5. //拷贝节点插入在原节点的后面
  6. struct Node*copy=(struct Node*)malloc(sizeof(struct Node));
  7. copy->val=cur->val;
  8. copy->next=cur->next;
  9. cur->next=copy;
  10. cur=copy->next;
  11. }
  12. //给拷贝节点的random赋值
  13. cur=head;
  14. while(cur)
  15. {
  16. struct Node*copy=cur->next;
  17. if(cur->random==NULL)
  18. {
  19. copy->random=NULL;
  20. }
  21. else
  22. {
  23. //控制random的值
  24. copy->random=cur->random->next;
  25. }
  26. cur=copy->next;
  27. }
  28. //将拷贝节点拆下来(尾插)
  29. cur=head;
  30. struct Node*newhead=NULL,*newtail=NULL;
  31. while(cur)
  32. {
  33. struct Node*copy=cur->next;
  34. struct Node*next=copy->next;
  35. if(newhead==NULL)
  36. {
  37. newhead=newtail=copy;
  38. }
  39. else
  40. {
  41. newtail->next=copy;
  42. newtail=newtail->next;
  43. }
  44. //恢复原链表结构,也可以不恢复
  45. cur->next=next;
  46. cur=next;
  47. }
  48. return newhead;
  49. }

今天的算法题就分享到这里吧!这些题目都是很经典的哦!

我们下期见!

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

闽ICP备14008679号