• 【单链表经典习题讲解】


     大家好呀,今天向大家分享的是关于单链表的一些比较基础且经典的题目,相信你看完了一定会对单链表的知识掌握的更加熟练。

    目录

    1 移除链表元素

    2 反转链表

    3 链表的中间结点

    4 链表中倒数第k个结点

     5 合并两个有序链表

    6 链表分割

    7 链表的回文结构

    8 相交链表

    9 环形链表

    10 环形链表


     

     

     



    1 移除链表元素

    题目描述:

    3faf08e7c1774f8a9c6e5512fa5e5301.png

     解题思路:

    遍历链表,用cur表示当前位置,记录要去除元素的前一位地址(prev),让prev->next=cur->next.然后不断迭代,直至cur为空.

    但是要额外处理当第一个元素就是要去除元素这种情况。

    具体代码实现:

    1. struct ListNode* removeElements(struct ListNode* head, int val)
    2. {
    3. struct ListNode* prev=NULL;
    4. struct ListNode* cur=head;
    5. while(cur)
    6. {
    7. if(cur->val == val)
    8. {
    9. //头删
    10. if(cur == head)
    11. {
    12. head=cur->next;
    13. free(cur);
    14. cur=head;
    15. }
    16. else
    17. {
    18. prev->next=cur->next;
    19. free(cur);
    20. cur=prev->next;
    21. }
    22. }
    23. else
    24. {
    25. prev=cur;
    26. cur=cur->next;
    27. }
    28. }
    29. return head;
    30. }

    结果展示:

    75594522d02f49b48770b50f5b15a771.png

     


    2 反转链表

    题目描述:

    009a4b42159f4c3ba17a56a050f5daf2.png

     解题思路1:

    遍历链表,让cur指向当前位置,记录cur前一位的位置prev,记录cur后一位位置next,让cur->next=prev,将prev=cur,cur=next,next=next->next,然后不断迭代,结束条件是cur为NULL,但是要注意当next为NULL时就不要进行next=next->next。

     789e8cdd2f444458838541f3dafbc98f.png

     具体代码:

    1. ListNode* reverseList(struct ListNode* head)
    2. {
    3. if(head==NULL)
    4. {
    5. return head;
    6. }
    7. struct ListNode* prev=NULL;
    8. struct ListNode* cur=head;
    9. struct ListNode* next=head->next;
    10. while(cur)
    11. {
    12. //翻转
    13. cur->next=prev;
    14. prev=cur;
    15. cur=next;
    16. if(next)
    17. {
    18. next=next->next;
    19. }
    20. }
    21. return prev;
    22. }

    结果展示:

    7bf692c311404d3695f76f67e79398bb.png

     

    解题思路2:

    遍历链表,将链表中的元素头插到一个新链表的头上,然后迭代下去,直到cur为NULL,但是要注意保存cur的下一个位置。

    具体代码:

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

    结果展示:

    f63813352cb54a0bafd45f25ff7791e9.png

     两种方法都是可行的,具体用哪一种可以自己选择,但是不要刻意去关注leetcode结果展示中的执行用时和内存消耗,这可能与网速有关。


    3 链表的中间结点

    题目描述:

    435500df58ec496d9aae0184b785630f.png

     解题思路:

    常规思路:遍历链表求链表长度,然后再遍历一遍数组求中间结点

    具体代码:

    1. struct ListNode* middleNode(struct ListNode* head)
    2. {
    3. int count=0;
    4. struct ListNode* cur=head;
    5. while(cur)
    6. {
    7. count++;
    8. cur=cur->next;
    9. }
    10. cur=head;
    11. int i=0;
    12. for(i=0;i2;i++)
    13. {
    14. cur=cur->next;
    15. }
    16. return cur;
    17. }

    这种方法可行,但是有没有其他方法只遍历一遍链表呢?

    快慢指针:让slow一次走一步,fast一次走两步,直到fast或者fast->next为NULL时结束,此时slow就是中间结点。

    具体代码:

    1. struct ListNode* middleNode(struct ListNode* head)
    2. {
    3. struct ListNode* slow=head;
    4. struct ListNode* fast=head;
    5. while(fast && fast->next)
    6. {
    7. slow=slow->next;
    8. fast=fast->next->next;
    9. }
    10. return slow;
    11. }

    结果展示:

    2b180bbac0164352ab497a2f1ed98441.png

     


    4 链表中倒数第k个结点

     

    题目描述:

    deabb4e1c2de483cbdb8ec770c630ac6.png

     解题思路:

    仍然用快慢指针解题,让快指针先走k步,然后同时走。

    具体代码:

    1. struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    2. // write code here
    3. struct ListNode* slow=pListHead;
    4. struct ListNode* fast=pListHead;
    5. while(k--)
    6. {
    7. if(!fast)
    8. return NULL;
    9. fast=fast->next;
    10. }
    11. while(fast)
    12. {
    13. slow=slow->next;
    14. fast=fast->next;
    15. }
    16. return slow;
    17. }

    其中值得注意的是当fast先走时并且当fast为空时直接返回NULL。

    结果展示:

    0b31656343f54542a55b20f909f88bd1.png

     5 合并两个有序链表

    题目描述:

    dd464e065c5e4bc7ac256c8f1188fd9a.png

     解题思路1:

    不创建头结点,一 一遍历两个链表,将较小的值拿下来尾插,然后迭代下去,直至其中一个链表为空,然后把另外一个链表剩下的结点全部链接。但是要注意考虑头为NULL的特殊情况。

    具体代码:

    1. struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
    2. {
    3. struct ListNode* head=NULL;
    4. struct ListNode* tail=NULL;
    5. if(list1==NULL)
    6. {
    7. return list2;
    8. }
    9. if(list2==NULL)
    10. {
    11. return list1;
    12. }
    13. while(list1 && list2)
    14. {
    15. if(list1->val < list2->val)
    16. {
    17. if(head==NULL)
    18. {
    19. tail=head=list1;
    20. }
    21. else
    22. {
    23. tail->next=list1;
    24. tail=tail->next;
    25. }
    26. list1=list1->next;
    27. }
    28. else
    29. {
    30. if(head==NULL)
    31. {
    32. tail=head=list2;
    33. }
    34. else
    35. {
    36. tail->next=list2;
    37. tail=tail->next;
    38. }
    39. list2=list2->next;
    40. }
    41. }
    42. if(list1)
    43. {
    44. tail->next=list1;
    45. }
    46. if(list2)
    47. {
    48. tail->next=list2;
    49. }
    50. return head;
    51. }

    结果展示:

    0bdb1be678f241d8832533ca1c3b9977.png

     解题思路2:

    创建一个头结点,不存储数据,方法与第一种类似,但是不需要考虑头为NULL的情况。

    具体代码:

    1. struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
    2. {
    3. if(list1==NULL)
    4. {
    5. return list2;
    6. }
    7. if(list2==NULL)
    8. {
    9. return list1;
    10. }
    11. struct ListNode* head=(struct ListNode*)malloc(sizeof(struct ListNode));
    12. struct ListNode* tail=head;
    13. while(list1 && list2)
    14. {
    15. if(list1->val < list2->val)
    16. {
    17. tail->next=list1;
    18. tail=tail->next;
    19. list1=list1->next;
    20. }
    21. else
    22. {
    23. tail->next=list2;
    24. tail=tail->next;
    25. list2=list2->next;
    26. }
    27. }
    28. if(list1)
    29. {
    30. tail->next=list1;
    31. }
    32. if(list2)
    33. {
    34. tail->next=list2;
    35. }
    36. struct ListNode* first=head->next;
    37. free(head);
    38. return first;
    39. }

    结果展示:

    a682a31fc44f4accbc1419d467866ff9.png

     这两种方法都是可行的,我个人更喜欢第二种方法。


    6 链表分割

    题目描述:

    d52f2d30b26b4ae6bef4b4fd7e5ebe17.png

    解题思路:

    创建两个头结点分别连接=x的链表,最后将较小链表的尾连接到大链表的头的下一位,最后别忘了给大链表的尾置NULL。

     848e4d452f114bc69d5b8caf6eef8dc9.png

    具体代码:

    1. class Partition {
    2. public:
    3. ListNode* partition(ListNode* pHead, int x) {
    4. // write code here
    5. ListNode*headSmall=(ListNode*)malloc(sizeof(ListNode));
    6. ListNode*tailSmall=headSmall;
    7. ListNode*headBig=(ListNode*)malloc(sizeof(ListNode));
    8. ListNode*tailBig=headBig;
    9. ListNode*cur=pHead;
    10. while(cur)
    11. {
    12. if(cur->val < x)
    13. {
    14. tailSmall->next=cur;
    15. tailSmall=cur;
    16. }
    17. else
    18. {
    19. tailBig->next=cur;
    20. tailBig=cur;
    21. }
    22. cur=cur->next;
    23. }
    24. tailSmall->next=headBig->next;
    25. tailBig->next=NULL;
    26. return headSmall->next;
    27. }
    28. };

     

     结果展示:

    cce15d7099d345dfb46fe44142122656.png

     


    7 链表的回文结构

    题目描述:

    f7b7666852d7418f80ca77778c9e127c.png

     解题思路:

    将链表看作两部分,前半部分和后半部分,再将后半部分逆置一一与前半部分比较,若相等就返回true,否则返回false.这里就不需要将前半部分的尾置为NULL。

    具体代码:

    1. struct ListNode* middleNode(struct ListNode* head)
    2. {
    3. struct ListNode* slow=head;
    4. struct ListNode* quick=head;
    5. while(quick && quick->next)
    6. {
    7. slow=slow->next;
    8. quick=quick->next->next;
    9. }
    10. return slow;
    11. }
    12. struct ListNode* reverseList(struct ListNode* head)
    13. {
    14. struct ListNode* cur=head;
    15. struct ListNode* newhead=NULL;
    16. while(cur)
    17. {
    18. struct ListNode* next=cur->next;
    19. cur->next=newhead;
    20. newhead=cur;
    21. cur=next;
    22. }
    23. return newhead;
    24. }
    25. class PalindromeList {
    26. public:
    27. bool chkPalindrome(ListNode* A) {
    28. // write code here
    29. ListNode* mid=NULL;
    30. mid=middleNode(A);
    31. ListNode* reverse=reverseList(mid);
    32. while(A && reverse)
    33. {
    34. if(A->val != reverse->val)
    35. {
    36. return false;
    37. }
    38. A=A->next;
    39. reverse=reverse->next;
    40. }
    41. return true;
    42. }
    43. };

     结果展示:

    afdac51315c8424e9e3a035b2bf1aab9.png

     


    8 相交链表

    题目描述:

    48361ec77b2c49a89db81eefce163214.png

     解题思路:

    常规思路是遍历其中一个链表,再将该链表与另外一个链表中每个结点比较,这样做时间复杂度为O(N^2),显然是不符合题意的。较为优的解题思路是先判断链表是否相交,然后求链表长度差值,让较长的链表先走差值步,再同时走,直到两个结点相等。

     具体代码:

    1. struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
    2. {
    3. struct ListNode *tailA=headA,*tailB=headB;
    4. int lenA=1;
    5. int lenB=1;
    6. while(tailA->next)
    7. {
    8. lenA++;
    9. tailA=tailA->next;
    10. }
    11. while(tailB->next)
    12. {
    13. lenB++;
    14. tailB=tailB->next;
    15. }
    16. if(tailA!=tailB)
    17. {
    18. return NULL;
    19. }
    20. int dis=fabs(lenA-lenB);
    21. struct ListNode *small=headB;
    22. struct ListNode *big=headA;
    23. if(lenA
    24. {
    25. small=headA;
    26. big=headB;
    27. }
    28. while(dis--)
    29. {
    30. big=big->next;
    31. }
    32. while(big!=small)
    33. {
    34. big=big->next;
    35. small=small->next;
    36. }
    37. return small;
    38. }

    结果展示:

    cfd00ad2dcde4750bc3f36758b836e33.png

     


    9 环形链表

    题目描述:

    7df9d9dae6bf45d092b0ca1116cb56d8.png

     解题思路:

    定义两个快慢指针,让慢指针一次走一步,快指针一次走两步,当快指针等于慢指针时就返回true,否则返回false.

    具体代码:

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

    结果展示:

    121e38cc89474ea58a6f5c28ed9c2a75.png

     题后深究:

    1 slow一次走一步,fast一次走两步,他们一定会相遇吗?

    2 若是slow一次走一步,fast一次走三步会怎样?fast一次走4步会怎样?

     

     063e102e7b194f248892e38bd20eaea3.png

    假设slow刚进环时slow与fast之间的距离为X,由于fast每次都比slow快一步,所以他们之间的相对距离在以1之间减少,所以肯定会相遇。

    如果fast一次走三步,当X为偶数时,他们一定相遇,当X为奇数时fast肯定在某个情况会恰好超过slow一位,这个时候他们能否相遇取决于C-1的奇偶情况(C表示环的长度),当C-1为奇数时永远不会相遇,当C-1为偶数时会相遇。

    a2a0487f18d14548b0b10e971f496db3.png

     当fast一次走四步时也是同样的分析方法。


    10 环形链表

    题目描述:

    d20ef29499144800b057c9a39a8a67e9.png

    解题思路:

    一个指针从相遇点开始走,一个指针从表头开始走,它们会在环的入口点相遇。

     代码实现:

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

    结果展示:

    d2713ff5a7624d3fa93d8a45e99e449f.png

     题后深究:

    为什么一个指针从相遇点开始走,一个指针从表头开始走,它们会在环的入口点相遇?

    07a77c8cce3e4b36ada6adad6e9a502d.png

     由图中公式可知,上述结论是成立的。

     

  • 相关阅读:
    Linux命令行
    一起Talk Android吧(第四百二十一回:绘图中添加阴影)
    GD32F303固件库开发(10)----双ADC轮询模式扫描多个通道
    【Qt 按钮】QPushButton所有函数和样式
    左支座零件的机械加工工艺规程及工艺装备设计【计算机辅助设计与制造CAD】
    Go 语言数组基础教程 - 数组的声明、初始化和使用方法
    男孩姓洪取什么名字好听
    【LeetCode19. 删除链表的倒数第 N 个结点】——链表,快慢双指针,虚拟头结点、利用栈
    信息学奥赛一本通:1310:【例2.2】车厢重组
    【会员管理系统】篇二之项目搭建、初始化、安装第三方库
  • 原文地址:https://blog.csdn.net/m0_68872612/article/details/126237995