• 【代码随想录】算法训练营 第三天 第二章 链表 Part 1


    目录

    链表基础

     链表的定义

    203. 移除链表元素

    题目

    思路

    代码

    直接删除法

    虚拟头结点辅助法

    707. 设计链表

    题目

    思路

    代码

    206. 反转链表

    题目

    思路

    代码

    双指针法

    递归法


    链表基础

    链表是一种通过指针串在一起的线性结构,每个节点都由数据域和指针域组成,数据域存放节点数据,指针域存放指向下一个节点的指针,最后一个节点的指针指向null,也即这个指针为空指针。

     链表的定义

    随想录中,标准的单链表定义如下:

    1. struct ListNode {
    2. int val; // 数据域里的数据
    3. ListNode *next; // 指针域里指向下个节点的指针
    4. ListNode(int x) : val(x), next(NULL) {} // 构造函数,直接定义并初始化一个节点的数据域值为x
    5. };
    6. ListNode* head = new ListNode(5); // 通过自己定义的构造函数来初始化节点,直接赋值为5
    7. ListNode* head = new ListNode();
    8. head->val = 5; // 使用默认的构造函数来初始化节点,但是这里需要自己赋值

    203. 移除链表元素

    题目

    思路

    力扣里已经定义好了链表,所以我们只需要使用ListNode* 来定义指针即可。

    这道题需要我们删除和目标值相同的节点,所以我们的思路简单粗暴,一个一个比下去然后遇到就删除就是了,但是这里有一个问题,就是我们要如何删除一个节点呢,其实很简单,要删除一个节点分三步,第一步,定义一个指针指向该节点,第二步,将原本指向该节点的指针指向这个节点的下一个节点,第三步,删除我们新定义的这个指针(同时把指针指向的节点也删了)。

    我们的代码有两种做法,一种是直接开干,将要删的节点分为头结点和中间节点,头结点先删,中间节点再用一个while语句来删;另一种是设置一个虚拟头结点,这样就不存在需要删除头结点的情况了,只需要删除中间节点即可,但是最后要注意,返回的是我们定义的虚拟头结点的下一个节点指针。

    代码

    直接删除法
    1. /**
    2. * Definition for singly-linked list.
    3. * struct ListNode {
    4. * int val;
    5. * ListNode *next;
    6. * ListNode() : val(0), next(nullptr) {}
    7. * ListNode(int x) : val(x), next(nullptr) {}
    8. * ListNode(int x, ListNode *next) : val(x), next(next) {}
    9. * };
    10. */
    11. class Solution {
    12. public:
    13. ListNode* removeElements(ListNode* head, int val) {
    14. while (head != NULL && head->val == val) {
    15. ListNode* tmp = head;
    16. head = head->next;
    17. delete tmp;
    18. }
    19. ListNode* cur = head;
    20. while (cur != NULL && cur->next != NULL) {
    21. if (cur->next->val == val) {
    22. ListNode* tmp = cur->next;
    23. cur->next = cur->next->next;
    24. delete tmp;
    25. }
    26. else {
    27. cur = cur->next;
    28. }
    29. }
    30. return head;
    31. }
    32. };
    虚拟头结点辅助法
    1. class Solution {
    2. public:
    3. ListNode* removeElements(ListNode* head, int val) {
    4. ListNode* dummy = new ListNode(0);
    5. dummy->next = head;
    6. ListNode* cur = dummy;
    7. while (cur->next != NULL) {
    8. if(cur->next->val == val) {
    9. ListNode* tmp = cur->next;
    10. cur->next = cur->next->next;
    11. delete tmp;
    12. }
    13. else {
    14. cur = cur->next;
    15. }
    16. }
    17. head = dummy->next;
    18. return head;
    19. }
    20. };

    707. 设计链表

    题目

    设计一个链表,实现以下功能:

    • 获取元素;
    • 表头添加元素;
    • 表尾添加元素;
    • 表中添加元素;
    • 删除元素。

    思路

    这就是最简单的手搓链表了(bushi),在这里我们可以像上面那样给链表的前面添加一个虚拟头结点dummy,这样就不用考虑对头结点的特殊情况了,所有节点都一视同仁。

    这就没什么思路不思路的了,思想很简单,实现是关键,真正写出来并且一点错没有,那就可以了,具体实现就看下面的代码吧。

    注意,当你要开始复习链表的时候,就照着这个代码多抄多背,以后面试再也不用担心!

    代码

    1. class MyLinkedList {
    2. public:
    3. // 定义链表节点结构体
    4. struct LinkedNode {
    5. int val;
    6. LinkedNode* next;
    7. LinkedNode(int val) : val(val), next(nullptr){}
    8. };
    9. // 初始化链表
    10. MyLinkedList() {
    11. dummy = new LinkedNode(0); // 定义一个虚拟头结点
    12. size = 0; // 链表的初始长度为0
    13. }
    14. // 获取第index个节点数值,如果index非法则直接返回-1,index从0开始
    15. int get(int index) {
    16. if (index < 0 || index > size - 1) {
    17. return -1;
    18. }
    19. LinkedNode* cur = dummy->next;
    20. while (index--) { // index可以看作数组下标,cur是从下标为0的节点开始的,所以这里循环index次没错
    21. cur = cur->next;
    22. }
    23. return cur->val;
    24. }
    25. // 在链表前面插入一个节点,插入完后新的节点称为链表的新头结点
    26. void addAtHead(int val) {
    27. LinkedNode* newNode = new LinkedNode(val);
    28. newNode->next = dummy->next;
    29. dummy->next = newNode;
    30. size++;
    31. }
    32. // 在链表最后添加一个节点
    33. void addAtTail(int val) {
    34. LinkedNode* newNode = new LinkedNode(val);
    35. LinkedNode* cur = dummy;
    36. while (cur->next != nullptr) {
    37. cur = cur->next;
    38. }
    39. cur->next = newNode;
    40. size++;
    41. }
    42. // 在链表中第index个节点前插入新节点
    43. void addAtIndex(int index, int val) {
    44. if (index > size) return;
    45. if (index < 0) index = 0;
    46. LinkedNode* newNode = new LinkedNode(val);
    47. LinkedNode* cur = dummy;
    48. while (index--) {
    49. cur = cur->next;
    50. }
    51. newNode->next = cur->next;
    52. cur->next = newNode;
    53. size++;
    54. }
    55. // 删除第index个节点
    56. void deleteAtIndex(int index) {
    57. if (index > size - 1 || index < 0) {
    58. return;
    59. }
    60. LinkedNode* cur = dummy;
    61. while (index--) {
    62. cur = cur->next;
    63. }
    64. LinkedNode* tmp = cur->next;
    65. cur->next = cur->next->next;
    66. delete tmp;
    67. tmp = nullptr;
    68. size--;
    69. }
    70. // 打印链表
    71. void print() {
    72. LinkedNode* cur = dummy;
    73. while (cur->next != nullptr) {
    74. cout << cur->next->val << " ";
    75. cur = cur->next;
    76. }
    77. cout << endl;
    78. }
    79. private:
    80. int size;
    81. LinkedNode* dummy;
    82. };

    206. 反转链表

    题目

    思路

    反转链表,就是从一个链表的第一个有效节点开始,逐一移出原链表,放到新链表的开头,

    这样虽然原链表是从前往后拿走节点,但是新链表是从后往前一个一个加进去,这就完成了反转。

    代码

    双指针
    1. class Solution {
    2. public:
    3. ListNode* reverseList(ListNode* head) {
    4. ListNode* temp; // 保存cur的下一个节点
    5. ListNode* cur = head; // 指向头结点
    6. ListNode* pre = NULL; // 新链表的头指针
    7. while (cur) { // 当原链表中还存在节点时
    8. temp = cur->next; // 保存cur的下一个节点,因为接下来要改变cur->next
    9. cur->next = pre; // 此时cur这个节点的下一个是新链表的第一个节点
    10. pre = cur; // 然后pre指针指向现在cur的这个结点
    11. cur = temp; // cur指向原链表的下一个结点
    12. }
    13. return pre;
    14. }
    15. };
    递归法
    1. class Solution {
    2. public:
    3. ListNode* reverse(ListNode* pre,ListNode* cur){
    4. if(cur == NULL) return pre;
    5. ListNode* temp = cur->next;
    6. cur->next = pre;
    7. // 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
    8. // pre = cur;
    9. // cur = temp;
    10. return reverse(cur,temp);
    11. }
    12. ListNode* reverseList(ListNode* head) {
    13. // 和双指针法初始化是一样的逻辑
    14. // ListNode* cur = head;
    15. // ListNode* pre = NULL;
    16. return reverse(NULL, head);
    17. }
    18. };

  • 相关阅读:
    python+nodejs+php+springboot+vue 法律知识分享科普系统平台
    .NET 8 的 green thread 异步模型被搁置了
    Redux 的困扰与如何技术选型
    如何理解死锁?
    Nodejs安装教程
    LED主流光源-环形光源
    [管理与领导-103]:IT经营者、管理者与所有者的关系
    【数模系列】01_聚类分析
    odoo关于计算字段store=True时导致的安装/更新时间较长问题的解决方案
    电力系统的延时潮流 (CPF)的计算【 IEEE-14节点】(Matlab代码实现)
  • 原文地址:https://blog.csdn.net/Summerison/article/details/133871364