• 代码随想录算法训练营15期 Day 3 | 203.移除链表元素 、707.设计链表 、206.反转链表


    今日任务 

    •  链表理论基础 
    •  203.移除链表元素 
    •  707.设计链表 
    •  206.反转链表 

    链表理论基础 

    链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

    单链表

     双链表

    循环链表---可以用来解决约瑟夫环的问题 

    数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。

    链表是通过指针域的指针链接在内存中各个节点。

    所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

    链表起始节点为2, 终止节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在一起。

    链表的构造函数

    1. // 单链表
    2. struct ListNode {
    3. int val; // 节点上存储的元素
    4. ListNode *next; // 指向下一个节点的指针
    5. ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
    6. };

    但是上面的这个写法有点怪异,可以使用下面的这个写法

    1. struct ListNode
    2. {
    3. double value;
    4. ListNode *next;
    5. //构造函数
    6. ListNode(double valuel, ListNode *nextl = nullptr)
    7. {
    8. value = value1;
    9. next = next1;
    10. }
    11. };

    注意点:nullptr与NULL的区别?

    ①在c++98和c++03之中,NULL默认情况下被定义为无符号整型常量0,否则为无类型指针常量 (void*) 0。

    1. #ifndef NULL
    2. #ifdef __cplusplus
    3. #define NULL 0
    4. #else
    5. #define NULL ((void *)0)
    6. #endif
    7. #endif

    继而下面两句的运行结果是相同的.

    1. void test(int)
    2. {
    3. cout << "test(int)" << endl;
    4. }
    5. int main()
    6. {
    7. test(0);
    8. test(NULL);
    9. return 0;
    10. }

    这是因为在 C++中,字面常量 0 既可以表示一个整形常量 0,也可以表示无类型指针常量 (void*) 0,但是编译器默认把它看成是一个整形常量 0 (如果把 0 当指针使用,就必须对其进行强转 (void*) 0 )。
    由于 NULL 被定义为 0,编译器默认 NULL 就是整形常量 0,所以 test(NULL) 调用 test(int) 函数,而非 test(int*) 函数。
    ②在c++11出来之后,C++11标准增加了新的关键字 nullptr,保证在任何情况下都表示空指针

    1. void test(int)
    2. {
    3. cout << "test(int)" << endl;
    4. }
    5. void test(int*)
    6. {
    7. cout << "test(int*)" << endl;
    8. }
    9. int main()
    10. {
    11. test(0);
    12. test(NULL);
    13. test(nullptr);
    14. return 0;
    15. }

    链表操作---删除节点

    注意此时,D任然在内存之中,需要手动释放掉才可以。

    链表操作---添加节点

    链表的增添和删除都是O(1)操作,也不会影响到其他节点。要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)。

    性能分析

    数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。
    链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。

    203.移除链表元素

    建议: 本题最关键是要理解 虚拟头结点的使用技巧,这个对链表题目很重要。
    题目链接:力扣

    思路一:头结点存在可能是删除值的可能.

    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. //思路一
    15. while(head!=nullptr && head->val == val)//while使用防止连续几个都是头结点为空
    16. {
    17. ListNode* cur = head;
    18. head = head->next;
    19. delete cur;
    20. }
    21. //头节点存在,并且不为空
    22. ListNode* cur1 = head;//注意:这个地方一定要进行相应的赋值,不能够直接操作head
    23. while(cur1!=nullptr && cur1->next!=nullptr)
    24. {
    25. if(cur1->next->val == val)
    26. {
    27. ListNode* tem = cur1->next;//这个地方也需要进行赋值,否则会出问题.
    28. cur1->next = cur1->next->next;
    29. delete tem;
    30. }
    31. else
    32. {
    33. cur1=cur1->next;
    34. }
    35. }
    36. return head;
    37. }
    38. };

    思路二:在头结点前面添加虚拟节点.

    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. //思路二:设置虚拟头节点
    15. ListNode* dummynode = new ListNode(0);
    16. dummynode->next = head;
    17. //ListNode* head = dummynode;
    18. ListNode* cur = dummynode;
    19. while(cur->next!=nullptr)
    20. {
    21. if(cur->next->val == val)
    22. {
    23. ListNode* temp = cur->next;
    24. cur->next = cur->next->next;
    25. delete temp;
    26. }
    27. else
    28. {
    29. cur = cur->next;
    30. }
    31. }
    32. head=dummynode->next;
    33. delete dummynode;
    34. return head;
    35. }
    36. };

    注意:内存回收机制,C++之中存在一个delete的内存删除过程,针对于指针而言.但是java之中存在一个自动回收内存的过程.
    C++标准规定:delete空指针是合法的,没有副作用。所以我们一般在delete后就以为万事大吉了,其实这是不安全的。delete释放指针指向的那部分内存,并不是指针本身的内存,在进行delete之后,指针还是会指向那块内存,如果要没有清空,会出现***空间不能够访问的异常.

    707.设计链表   

    建议: 这是一道考察 链表综合操作的题目,不算容易,可以练一练 使用虚拟头结点

    题目链接:力扣

    这个地方需要注意的地方是更新边的过程,先更新哪个边.下面的代码是我自己编写的,我感觉没有错误,但是就是编译不出来,不知道啥原因.

    1. class MyLinkedList {
    2. public:
    3. //1.首先建立链表操作
    4. struct ListNode
    5. {
    6. int val;
    7. ListNode* next;
    8. ListNode(int val1,ListNode* next1 = nullptr)
    9. {
    10. val1 = val;
    11. next1 = next;
    12. }
    13. };
    14. //2.声明大小和节点
    15. ListNode* dummyHead;
    16. int size;
    17. MyLinkedList() {
    18. dummyHead = new ListNode(0);
    19. size = 0;
    20. }
    21. int get(int index) {
    22. if(index < 0 || index >= size)
    23. {
    24. return -1;
    25. }
    26. ListNode* cur = dummyHead;
    27. while(index--)//直接将index进行一个减小的过程
    28. {
    29. cur = cur->next;
    30. }
    31. return cur->next->val;
    32. }
    33. void addAtHead(int val) {
    34. //注意头结点为空也是可以的
    35. ListNode* newHead = new ListNode(val);
    36. ListNode* cur = dummyHead;
    37. //注意插入的顺序,先是插入后面的元素,然后才是进行插入前面的元素
    38. newHead->next = cur->next;
    39. cur->next=newHead;
    40. //一定要注意
    41. size++;
    42. }
    43. void addAtTail(int val) {
    44. ListNode* newNode = new ListNode(val);
    45. ListNode* cur = dummyHead;
    46. while(cur->next != nullptr){
    47. cur = cur->next;
    48. }
    49. cur->next = newNode;
    50. size++;
    51. }
    52. void addAtIndex(int index, int val) {
    53. if(index<0||index>(size-1))
    54. {
    55. return;
    56. }
    57. ListNode* cur = dummyHead;
    58. ListNode* newHead = new ListNode(val);
    59. while(index--)
    60. {
    61. cur=cur->next;
    62. }
    63. newHead->next = cur->next;
    64. cur=newHead;
    65. size++;
    66. }
    67. void deleteAtIndex(int index) {
    68. if(index<0||index>(size-1))
    69. {
    70. return;
    71. }
    72. ListNode* cur = dummyHead;
    73. while(index--)
    74. {
    75. cur=cur->next;
    76. }
    77. ListNode* tem = cur->next;
    78. cur->next= cur->next->next;
    79. delete tem;
    80. tem = nullptr;
    81. size--;
    82. }
    83. };
    84. /**
    85. * Your MyLinkedList object will be instantiated and called as such:
    86. * MyLinkedList* obj = new MyLinkedList();
    87. * int param_1 = obj->get(index);
    88. * obj->addAtHead(val);
    89. * obj->addAtTail(val);
    90. * obj->addAtIndex(index,val);
    91. * obj->deleteAtIndex(index);
    92. */

    206.反转链表 

    题目链接:力扣

    本题有两种接法,一种是双指针解法,一种是进行递归求解.递归求解的方式参考意义不大,个人认为,因此这里只写双指针的解法.其实理解了很好实现,不理解就是很难去实现。

    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* reverseList(ListNode* head) {
    14. ListNode* pre = nullptr;
    15. ListNode* cur = head;
    16. while(cur!=nullptr)
    17. {
    18. ListNode* tem= cur->next;
    19. cur->next=pre;
    20. pre=cur;
    21. cur=tem;
    22. }
    23. return pre;
    24. }
    25. };

  • 相关阅读:
    工业4.0数字孪生下的应用案例
    Jansson库中的json_delete函数
    day28--JS(同步异步代码,回调函数地狱,promise链式调用,async函数和await,事件循环,宏任务与微任务)
    Simple RPC - 02 通用高性能序列化和反序列化设计与实现
    JVM垃圾回收系列之垃圾收集算法
    如何启动一个Vue项目
    每天20题吃透这份将近 500 页的“Java 工程师八股文”,成功入职阿里
    【html5期末大作业】基于HTML+CSS+JavaScript管理系统页面模板
    vulnhub靶机-DC系列-DC-3
    基于Python的高校网络课程数据分析
  • 原文地址:https://blog.csdn.net/m0_47489229/article/details/130884782