• 力扣练习——链表在线OJ


    目录

    提示:

    一、移除链表元素

    题目:

    解答:

    二、反转链表

    题目:

    解答:

    三、找到链表的中间结点

    题目:

    解答:

    四、合并两个有序链表(经典)

    题目:

    解答:


    提示:

    ①:接上一篇文章http://t.csdnimg.cn/vvmIr

    本次我们来做一些在线OJ题,进一步加深印象和感觉,并且本次某些方法会沿用上一篇文章的,所以感兴趣的小伙伴可以参考一下上一篇文章。

    ②:对于这些在线OJ,小编会把具体步骤放在注释里面,然后小编没有画图,大家可自行画图然后跟着解析思路一起思考。

    ③:链表的在线OJ一般都是用的无哨兵的头指针,所以大家一定要搞懂有哨兵和无哨兵的区别。

    一、移除链表元素

    题目:

    解答:

    思路:只需要一边遍历链表,一边进行数据域的比较。若找到指定的值val,则删除该结点;

    又因为删除一个结点,我们必须知道该结点的前一个结点,所以采用双指针的思路:

    一个指针prve用于记录指定结点的前一个结点;

    一个指针cur用于遍历寻找。

    另外,找的过程中会发生两种情况;

    情况一:指定结点为头结点(即头指针的位置):这时我们需要考虑头指针head的变化,具体实现看注释;

    情况二:指定结点为一般结点:这时我们就依靠前一个结点正常删除即可;

    源代码即注释如下:
     

    1. struct ListNode* removeElements(struct ListNode* head, int val)
    2. {
    3. //双指针prev、cur
    4. struct ListNode* prev = NULL, *cur = head;
    5. //当cur==NULL。即过了尾结点,遍历完成,退出循环
    6. while (cur)
    7. {
    8. //数据域相等,说明找到要删的结点
    9. if (cur->val == val)
    10. {
    11. //分为两种情况,一是头删,二是正常删除
    12. if (cur == head)//头删
    13. {
    14. head = cur->next;
    15. free(cur);
    16. cur = head;
    17. }
    18. //正常删
    19. else
    20. {
    21. prev->next = cur->next;
    22. free(cur);
    23. cur = prev->next;
    24. }
    25. }
    26. //数据域不相同,则不是指定结点,向后走一步
    27. else
    28. {
    29. prev = cur;
    30. cur = cur->next;
    31. }
    32. }
    33. return head;
    34. }

    二、反转链表

    题目:

    解答:

    思路:我们可以将当前结点的next域指向前一结点,但在此之前我们应该把当前结点的前一个结点和后一个结点给保存下来,防止结点丢失;

    所以我们可以创建两个临时指针prve和cur;

    cur用于保存下一个结点;

    prve用于保存前一个结点;

    而头结点head就用于保存当前结点;

    1. struct ListNode* reverseList(struct ListNode* head)
    2. {
    3. //两个临时指针
    4. struct ListNode* cur = head, * prve = NULL;
    5. //因为cur是用于保存下一个结点,所以当cur为空时,即遍历完链表,循环结束
    6. while (cur)
    7. {
    8. //cur保存下一个结点
    9. cur = cur->next;
    10. //将当前结点的next域指向前一个结点
    11. head->next = prve;
    12. //将prve保存当前结点,也就是保存下一轮操作的"前一个结点"
    13. prve = head;
    14. //完成一轮操作,head重定位当前结点
    15. head = cur;
    16. }
    17. //最后当head和cur都为NULL时,prve作为前一个结点即为"原链表的尾结点,反转链表的首结点”,所以返回prve
    18. return prve;
    19. }

    三、找到链表的中间结点

    题目:

    解答:

    这道题用到一个经典思路叫:“快慢指针”;

    即定义两个指针;

    一个快指针fast,一次向后跳过两个结点;

    一个慢指针slow,一次向后跳过一个结点;

    初略想一下,当fast指针遍历完数组后,solw指针就是我们想要的中间结点;

    但这里要分奇数个结点还是偶数个结点,如下:

    奇数个结点的情况:

    偶数个结点的情况:

    所以结束有两个可能,即快指针指向尾结点或者快指针指向NULL;

    1. struct ListNode* middleNode(struct ListNode* head)
    2. {
    3. //快指针fast,慢指针slow
    4. struct ListNode* fast = head, * slow = head;
    5. //因为不确定奇数个还是偶数个,所以当快指针任意满足一种情况,则结束
    6. while (fast && fast->next)
    7. {
    8. //慢指针走一步
    9. slow = slow->next;
    10. //快指针走两步
    11. fast = fast->next->next;
    12. }
    13. return slow;
    14. }

    四、合并两个有序链表(经典)

    题目:

    解答:

    ①:由合并升序链表,我们可以联想到合并升序顺序表(数组),我们知道是将大的一个数放在大空间的末尾,第二大的数放在大空间的倒数第二个位置,重复此操作,直到短的一方比较完,再把长的一方剩下的元素连接在末尾;

    ②:但链表和顺序表有一点区别是,链表只需要逻辑上相邻即可;

    ③:所以我们可以创建两个临时指针:

    一个临时指针head用于充当新合并链表的头指针;

    一个临时指针tail用于插入操作;

    ④:具体我们只需要将两个链表中的元素依次比较,再将数据域小的结点尾插到tail链表后面,因为tail和head指向同一个链表,只是head是头指针,过程中是tail再变,所以要注意tail指针的变化,最后返回head头指针即可。

    ⑤:其中我们可能遇到三种情况:

    情况一、链表一或链表二为空,则直接返回另一个链表;

    情况二、链表一和链表二长度相等,则循环上述操作;

    情况三、长度不相等,则操作到某一时刻,链表一或二就会为NULL,这时就会出循环,然后再将长的链表剩下的元素插到tail结点后面。

    1. //将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
    2. struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
    3. {
    4. //情况一、表一或表二为NULL,直接返回另一个表
    5. if (list1 == NULL)
    6. return list2;
    7. if (list2 == NULL)
    8. return list1;
    9. //情况二、按正常操作来进行
    10. struct ListNode* head = NULL, * tail = NULL;
    11. while (list1 && list2)
    12. {
    13. //依次比较数据域
    14. if (list1->val < list2->val)
    15. {
    16. //第一次需要将head和tail指针指向首结点较小的表,以便后续能尾插结点
    17. if (tail == NULL)
    18. {
    19. head = tail = list1;
    20. }
    21. //尾插
    22. else
    23. {
    24. tail->next = list1;
    25. tail = tail->next;
    26. }
    27. list1 = list1->next;
    28. }
    29. else
    30. {
    31. //第一次需要将head和tail指针指向首结点较小的表,以便后续能尾插结点
    32. if (tail == NULL)
    33. {
    34. head = tail = list2;
    35. }
    36. //尾插
    37. else
    38. {
    39. tail->next = list2;
    40. tail = tail->next;
    41. }
    42. list2 = list2->next;
    43. }
    44. }
    45. //情况三、两个表长度不相等,会有一个表有剩余
    46. //因为是升序表,所以直接将剩余的表插入tail的next域即可
    47. if (list1)
    48. {
    49. tail->next = list1;
    50. }
    51. if (list2)
    52. {
    53. tail->next = list2;
    54. }
    55. //最后返回合并的表的头指针
    56. return head;
    57. }

    本次知识到此结束,希望对你有所帮助!

  • 相关阅读:
    css颜色渐进
    【持续更新中】Dart语言核心知识梳理
    SpringBoot3.1.5对应新版本SpringCloud开发(1)-Eureka注册中心
    Azure Service Principals ----- Azure 上最好保守的秘密的服务
    本地快速让某个目录变成服务器访问
    vector实现——memcpy拷贝问题
    【C#】跨平台UI库Avalonia的学习笔记
    架构思考 (六)
    DRF的认证组件(源码分析)
    【OpenCV】-物体的凸包
  • 原文地址:https://blog.csdn.net/hffh123/article/details/133518203