一、链表理论基础

1. 什么是链表

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

链表的入口节点称为链表的头节点,也就是 head。
如图所示:
image.png

2. 链表的类型

(1) 单链表

  1. 单链表

上面说的就是单链表

(2)双链表

  1. 双链表

单链表中的节点只能指向节点的下一个节点。
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表既可以向后查询,也可以向前查询。

如图所示:
image.png

(3)循环链表

循环链表,顾名思义,就是将链表的首尾相连。
循环链表可以用来解决 约瑟夫环 问题。
image.png

3. 链表的存储方式

数组在内存中的分布是连续的,但是链表在内存中可不是连续分布的。
链表是通过指针域的指针链接在内存中的各个节点,组成的一串不连续的存储空间。
所以链表中的节点在内存中不是连续分布的,而是散乱分布在内存中的某个地址上,分配机制取决于操作系统的内存管理。

如图所示:
image.png
这个链表的起始节点为2,终止节点为7,各个节点分布在内存的不同地址空间上,通过指针串联在一起。

4. 链表的定义

链表节点的定义,看似简单,但实际上很多人面试的时候都写不好。
这是因为平时在刷 LeetCode 的时候,链表的节点都默认定义好了,直接用就行了,所以大家都没有注意到链表的节点是如何定义的。
而在面试的时候,我们是需要自己手写链表的。

以下是 C/C++定义链表节点的方式:

  1. // 单链表
  2. class ListNode{
  3. int val; // 数据域
  4. ListNode *next; // 指针域
  5. ListNode(int x) : val(x), next(null) {} // 节点的构造函数
  6. }

链表的定义可以不写构造函数吗?答案是可以的,C++会帮我们生成一个默认构造函数。
但是这个构造函数不会初始化任何成员变量。
举个例子:

  1. // 自己写了构造函数
  2. ListNode *head = new ListNode(5);
  1. // 使用默认构造函数
  2. ListNode * head = new ListNode();
  3. head->val = 5;

如果不定义构造函数,使用默认构造函数的话,在初始化的时候就不能直接给变量赋值!

5. 链表的操作

(1)删除节点

删除节点D,如下:
image.png
只要将 C 节点的 next 指针指向 E 节点就可以了。
那有人会说了,D节点不是依然存留在内存里吗?只不过是没有在这个链表里而已。
确实是这样,所以在C++里最好是再手动释放这个D节点,释放这块内存。
其他的语言,例如Java、Python,就有自己的内存回收机制,不再需要程序员手动释放了。

(2)添加节点

如图所示:
image.png
可以看出链表的增添和删除都是 O(1) 操作,也不会影响到其他节点。
但是要注意,要是删除第五个节点,需要从头节点查找到第四个节点,然后通过next指针进行删除操作,查找的时间复杂度是 O(n)。

6. 性能分析

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

二、移除链表元素

1. 移除链表元素

(1)能够访问头节点

203. 移除链表元素 - 力扣(LeetCode)

我觉得代码随想录已经说的很好了,而且这道题也简单,看代码随想录的题解就可以了。移除链表元素

这是我的代码:

  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){
  16. ListNode* temp = head;
  17. head = head->next;
  18. delete temp; // 释放内存
  19. }
  20. // 如果删除的不是头节点
  21. ListNode* curptr = head;
  22. while(curptr != nullptr && curptr->next != nullptr){
  23. if(curptr->next->val == val){
  24. ListNode* temp = curptr->next;
  25. curptr->next = curptr->next->next;
  26. delete temp;
  27. }
  28. else{
  29. curptr = curptr->next;
  30. }
  31. }
  32. return head;
  33. }
  34. };

(2)无法访问头节点

237. 删除链表中的节点 - 力扣(LeetCode)

方法:和下一个节点交换

删除链表中的节点的常见的方法是定位到待删除结点的上一个结点,修改上一个结点的 next 指针,使其指向待删除节点的下一个节点,即可完成删除操作。

这道题中,传入的参数 node 为要被删除的节点,无法定位到该节点的上一个节点。注意到要被删除的节点不是链表的末尾节点,因此 node.next 不为空,可以通过对 node 和 node.next 进行操作实现删除节点。

在给定节点 node 的情况下,可以通过修改 node 的 next 指针的指向,删除 node 的下一个节点。但是题目要求删除 node,为了达到删除 node 的效果,只要在删除节点之前,将 node 的节点值修改为 node.next 的节点值即可。
例如:给定链表 4 —> 5 —> 1 —> 9,要被删除的节点值是 5,即链表中的第2个接待你。可以通过如下两步操作实现删除节点的操作。

  1. 将第2个节点的值修改为第3个节点的值,即将节点5的值修改为1,此时链表如下:

4 —> 5 —> 1 —> 9

  1. 删除第3个节点,此时链表如下:

4 —> 1 —> 9
至此,题解。

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * ListNode *next;
  6. * ListNode(int x) : val(x), next(NULL) {}
  7. * };
  8. */
  9. class Solution {
  10. public:
  11. void deleteNode(ListNode* node) {
  12. // 因为题目给的是要删除当前的节点,所以获取不到当前节点的前一个结点
  13. // 但是题目又说,被删除的结点不是尾节点,所以我们想到当前将下一个节点的赋给当前要删除的节点,然后删掉下一个节点,这样就等价于删除了当前节点
  14. // 因为这样,即使题目要删除的是头节点,我们通过交换,删除的是下一个节点,所以不用分情况讨论
  15. ListNode* temp = node->next;
  16. node->val = node->next->val;
  17. node->next = node->next->next;
  18. delete temp; // 释放内存
  19. }
  20. };

2. 设计链表

(1)设计一个完整的链表

707. 设计链表 - 力扣(LeetCode)

写这道题主要是对链表还不是很熟悉,而且卡在一个地方,就是在编译器写的时候是可以使用内部类的,但是在力扣平台上不知道为什么用不了,所以写了结构体,当然这个结构体也可以提到类的外部。其他的都没什么大问题,多写就行了。

  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. MyLinkedList() { // 构造函数
  10. _dummyHead = new LinkedNode(0);
  11. _size = 0; // 虚拟头节点不算入真正链表的结点
  12. }
  13. // 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点
  14. int get(int index) {
  15. if(index < 0 || index > _size - 1){ // 索引无效
  16. return -1;
  17. }
  18. LinkedNode* curptr = _dummyHead; // 虚拟头指针赋给当前需要操作的指针
  19. while(index--){
  20. curptr = curptr->next;
  21. }
  22. return curptr->next->val;
  23. }
  24. void addAtHead(int val) {
  25. LinkedNode* newHead = new LinkedNode(val); // 新的头节点
  26. newHead->next = _dummyHead->next;
  27. _dummyHead->next = newHead;
  28. _size++;
  29. }
  30. void addAtTail(int val) {
  31. LinkedNode* newTail = new LinkedNode(val); // 新的尾节点
  32. LinkedNode* curptr = _dummyHead;
  33. while(curptr->next != nullptr){ // 定位到尾节点
  34. curptr = curptr->next;
  35. }
  36. curptr->next = newTail;
  37. _size++;
  38. }
  39. // 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
  40. // 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
  41. // 如果index大于链表的长度,则返回空
  42. void addAtIndex(int index, int val) {
  43. if(index > _size){
  44. return;
  45. }
  46. LinkedNode* newNode = new LinkedNode(val);
  47. LinkedNode* curptr = _dummyHead;
  48. while(index--){ // 找到要插入位置的前一个节点
  49. curptr = curptr->next;
  50. }
  51. newNode->next = curptr->next;
  52. curptr->next = newNode;
  53. _size++;
  54. }
  55. void deleteAtIndex(int index) {
  56. if(index < 0 || index > _size - 1){
  57. return;
  58. }
  59. LinkedNode* curptr = _dummyHead;
  60. while(index--){
  61. curptr = curptr->next;
  62. }
  63. LinkedNode* temp = curptr->next;
  64. curptr->next = curptr->next->next;
  65. delete temp;
  66. _size--;
  67. }
  68. // 打印链表
  69. void display(){
  70. LinkedNode* cur = _dummyHead->next;
  71. while(cur != nullptr){
  72. cur = cur->next;
  73. cout << cur->val << " ";
  74. }
  75. cout << endl;
  76. }
  77. private:
  78. LinkedNode* _dummyHead; // 虚拟头节点
  79. int _size; // 链表的节点数,即链表的大小
  80. };
  81. /**
  82. * Your MyLinkedList object will be instantiated and called as such:
  83. * MyLinkedList* obj = new MyLinkedList();
  84. * int param_1 = obj->get(index);
  85. * obj->addAtHead(val);
  86. * obj->addAtTail(val);
  87. * obj->addAtIndex(index,val);
  88. * obj->deleteAtIndex(index);
  89. */

扩展:可以试试用 双链表 来解,双链表需要开 一个虚拟头节点 和 一个虚拟尾节点,同时需要一个 _size 来记录链表的大小,我们通过 _size - index 更接近 虚拟头 还是 虚拟尾,来确定从那边开始操作,这样会让程序的效率提高。

3. 反转链表

(1)反转整个链表

① 迭代的思想

206. 反转链表 - 力扣(LeetCode)
思路:
如果再定义一个新的链表,实现链表元素的反转,其实这是对内存空间的浪费。
其实只需要改变链表的 next 指针的指向,直接将链表反转,而不用重新定义一个新的链表,如图所示:

image.png

之前链表的头节点是元素1,反转之后头节点就是元素5,这里并没有添加或者删除节点,仅仅是改变next指针的方向。
接下来看一看是如何反转的?
008eGmZEly1gnrf1oboupg30gy0c44qp.gif
双指针法:

  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* temp; // 保存下一个节点的指针
  15. ListNode* pre = nullptr; // pre指向空指针
  16. ListNode* cur = head; // cur指向头指针
  17. while(cur != nullptr){ // cur需要遍历一遍指针
  18. temp = cur->next; // 先将下一个节点保存再temp
  19. cur->next = pre; // 实现第i个节点的反转
  20. // 更新pre和cur指针,因为最后我们要通过pre来访问反转后的链表
  21. pre = cur;
  22. cur = temp;
  23. }
  24. return pre;
  25. }
  26. };

② 递归思想

力扣有位大牛 labuladong 对递归反转链表总结的很好https://mp.weixin.qq.com/s/5wz_YJ3lTkDH3nWfVDi5SA

递归的思想相对迭代思想,稍微有点难以理解,处理的技巧是:不要跳进递归,而是利用明确的定义来实现算法逻辑。
处理看起来比较困难的问题,可以尝试化整为零,把一些简单的解法进行修改,解决困难的问题。
值得一提的是,递归操作链表并不高效。和迭代解法相比,虽然时间复杂度都是O(n),但是迭代解法的空间复杂度是O(1),而递归解法需要堆栈,空间复杂度是O(n)。但是用递归写的代码看起来更加简洁、优美。考虑效率的话还是使用迭代算法更高。

  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. if(head == nullptr){
  15. return head;
  16. }
  17. if(head->next == nullptr){ // 递归出口,如果要反转的节点只剩下一个,则直接返回本节点
  18. return head;
  19. }
  20. ListNode* last = reverseList(head->next);
  21. head->next->next = head; // 让下一个节点指向它的上一个节点
  22. head->next = nullptr; // 上一个节点指向空
  23. return last; // 递归完成后last指针指向原来链表的最后一个节点,也就是反转后的第一个节点
  24. }
  25. };

(2)回文链表

垃圾算法,时间复杂度 O(3n),空间复杂度 O(n)

  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. bool isPalindrome(ListNode* head) {
  14. ListNode* temp;
  15. ListNode* pre = nullptr;
  16. ListNode* cur = head; // cur指针用来遍历链表
  17. // 开了一个新链表,垃圾算法来着
  18. ListNode* newHead = new ListNode();
  19. ListNode* ptr = newHead;
  20. while(cur != nullptr){
  21. ListNode* newNode = new ListNode();
  22. newNode->val = cur->val;
  23. ptr->next = newNode;
  24. ptr = ptr->next;
  25. cur = cur->next;
  26. }
  27. cur = head;
  28. ptr = newHead->next;
  29. while(cur != nullptr){
  30. temp = cur->next; // 指向下一个节点
  31. cur->next = pre;
  32. // 更新pre和cur
  33. pre = cur;
  34. cur = temp;
  35. }
  36. while(pre != nullptr && ptr != nullptr){
  37. if(pre->val != ptr->val){
  38. return false;
  39. }
  40. pre = pre->next;
  41. ptr = ptr->next;
  42. }
  43. return true;
  44. }
  45. };

时间复杂度 O(n),空间复杂度 O(1)

思路:
image.png
代码:

  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. // 反转链表
  14. ListNode* reverse(ListNode* head){
  15. ListNode* temp = nullptr;
  16. ListNode* pre = nullptr;
  17. ListNode* cur = head; // cur指针用来遍历链表
  18. while(cur != nullptr){
  19. temp = cur->next; // temp保存cur下一个节点的指针域
  20. cur->next = pre; // 让cur指向nullptr
  21. pre = cur; // 更新pre和cur
  22. cur = temp;
  23. }
  24. return pre; // pre才是反转后链表的头节点
  25. }
  26. bool isPalindrome(ListNode* head) {
  27. if(head == nullptr || head->next == nullptr){
  28. return true;
  29. }
  30. ListNode* slow = head; // 慢指针,每次移动一个位置,用来记录链表的中间位置
  31. ListNode* fast = head; // 快指针,每次移动两个位置,走完链表,让slow能够走到链表的中间位置
  32. ListNode* pre = head; // pre指向slow的前一个位置,用来分割链表
  33. while(fast != nullptr && fast->next != nullptr){ // 目的是让slow能够指向链表的中间位置
  34. pre = slow; // pre指向slow的前一个位置
  35. slow = slow->next;
  36. fast = fast->next->next;
  37. }
  38. pre->next = nullptr; // 将链表前半部分断开
  39. ListNode* cur1 = head; // 链表前半段
  40. ListNode* cur2 = reverse(slow); // 链表后半段
  41. while(cur1 != nullptr){ // 两个链表开始比较,cur1段链表的长度一定小于cur2,所以使用cur1作为循环的长度
  42. if(cur1->val != cur2->val){
  43. return false;
  44. }
  45. cur1 = cur1->next;
  46. cur2 = cur2->next;
  47. }
  48. return true;
  49. }
  50. };

4. 交换链表中的节点

(1)两两交换链表中的节点

24. 两两交换链表中的节点 - 力扣(LeetCode)

思路:
一定要画图。
情况一:
image.png
情况二:
image.png
先定义一个快指针和一个慢指针,指向交换节点前的链表的头(也就是节点1),因为题目不允许修改节点的值,所以只能改变节点指针域的指向以达到交换节点的效果。首先要考虑清楚链表的结点数量是偶数个还是奇数个?偶数个意味着每两个节点都需要交换,那么 slow->next = fast->next->next->next;如果是奇数个,那么 slow->next = fast->next->next;
注意:交换后最后一个节点的指针域一定要指向一个空指针,不然链表就死循环了。

代码:

  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* swapPairs(ListNode* head) {
  14. if(head == nullptr || head->next == nullptr){ // 如果是空链表或只有一个节点,那么直接返回
  15. return head;
  16. }
  17. ListNode* slow = head;
  18. ListNode* fast = head;
  19. head = head->next;
  20. while(fast && fast->next){ // fast或fast->next为空,表明没有节点或只有一个节点,这时不再需要交换了
  21. fast = fast->next->next;
  22. slow->next->next = slow;
  23. if(fast == nullptr){ // 如果fast节点不存在,那么slow->next则需要指向一个空指针,不然链表陷入死循环
  24. slow->next = nullptr;
  25. }
  26. else{
  27. if(fast->next == nullptr){ // 如果 fast->next不存在,那么表明链表的个数是奇数,最后一个节点不需要交换(因为没有)
  28. slow->next = fast;
  29. }
  30. else{ // 链表节点是偶数个
  31. slow->next = fast->next;
  32. }
  33. }
  34. slow = fast;
  35. }
  36. return head;
  37. }
  38. };

5. 删除链表的倒数第N个节点

19. 删除链表的倒数第N个节点 - 力扣(LeetCode)

我的解法
时间复杂度O(n)
控件复杂度O(1)

  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* removeNthFromEnd(ListNode* head, int n) {
  14. ListNode* dummyHead = new ListNode(-1); // 虚拟头节点
  15. dummyHead->next = head;
  16. ListNode* cur = head;
  17. int size = 0; // 记录链表的大小
  18. while(cur != nullptr){
  19. cur = cur->next;
  20. size++;
  21. }
  22. int moveNumber = size - n; // 链表正向移动的节点数
  23. cur = dummyHead; // cur指针指向虚拟头节点
  24. while(moveNumber > 0){
  25. cur = cur->next;
  26. moveNumber--;
  27. }
  28. cur->next = cur->next->next; // 删除指定的节点
  29. return dummyHead->next;
  30. }
  31. };

递归思想:

  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. int cur = 0;
  14. ListNode* removeNthFromEnd(ListNode* head, int n) {
  15. if(head == nullptr){ return nullptr; }
  16. // 这里为什么要用 head->next 来接收递归的返回值呢?很巧妙的,当递归到最后一层时,递归开始往回走
  17. // 它会返回最后一个节点(形参head->next),即相当于把 head->next 返回给 head->next,这样子head->next也会跟着往前走
  18. // 知道走到链表的第一个节点的位置。
  19. head->next = removeNthFromEnd(head->next, n);
  20. cur++;
  21. if(cur == n){ return head->next; } // 如果当前的节点是要删除的节点,就返回它的下一个节点,即我们忽略要删除的节点
  22. return head;
  23. }
  24. };

6. 链表相交

面试题 02.07. 链表相交 - 力扣(LeetCode)

思路:
简单来说,就是求两个链表相交的指针,注意这里的相交指的并不是链表节点的值相同,而是指针相同。
我们可以求出两个链表的长度,然后求出他们的长度差 num,通过这个长度差,让更长的链表先移动 num个节点数,然后这个时候他们就在同一起跑线上了,我们可以同时让他们往后移动,判断这两个指针是否相等就行了,至此题解。

复杂度分析:
时间复杂度O(n + m)
空间复杂度O(1)

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * ListNode *next;
  6. * ListNode(int x) : val(x), next(NULL) {}
  7. * };
  8. */
  9. class Solution {
  10. public:
  11. ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
  12. ListNode* A_head = headA;
  13. ListNode* B_head = headB;
  14. int A_size = 0; // 记录链表A和链表B的长度
  15. int B_size = 0;
  16. while(A_head != nullptr){ // 记录A的大小
  17. A_head = A_head->next;
  18. A_size++;
  19. }
  20. while(B_head != nullptr){ // 记录B的大小
  21. B_head = B_head->next;
  22. B_size++;
  23. }
  24. A_head = headA;
  25. B_head = headB;
  26. if(A_size < B_size){ // 不管A和B谁更长,最终我们都让A_head指向更长的那条链表
  27. swap(A_size, B_size);
  28. swap(A_head, B_head);
  29. }
  30. int num = A_size - B_size; // A、B链表节点的数量差
  31. while(num > 0){ // 链表A先移动 num 个节点
  32. A_head = A_head->next;
  33. num--;
  34. }
  35. while(A_head != nullptr){
  36. if(A_head == B_head){ return A_head; }
  37. A_head = A_head->next;
  38. B_head = B_head->next;
  39. }
  40. return nullptr;
  41. }
  42. };

7. 环形链表

(1)环形链表II

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

思路:
代码随想录,环形链表 II

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * ListNode *next;
  6. * ListNode(int x) : val(x), next(NULL) {}
  7. * };
  8. */
  9. class Solution {
  10. public:
  11. ListNode *detectCycle(ListNode *head) {
  12. if(head != NULL && head->next != NULL){
  13. ListNode* cur = head;
  14. ListNode* slow = head; // 慢指针
  15. ListNode* fast = head; // 快指针
  16. while(fast != NULL && fast->next != NULL){ // 若fast指针或它的下一个指针为空,表明链表有尽头,即没有环
  17. fast = fast->next->next; // fast指针每次移动两个位置
  18. slow = slow->next; // slow指针每次移动一个位置
  19. if(slow == fast){ // 如果相遇表示链表存在环
  20. ListNode* index1 = fast;
  21. ListNode* index2 = head;
  22. while(index1 != index2){
  23. index1 = index1->next;
  24. index2 = index2->next;
  25. }
  26. return index2;
  27. }
  28. cur = cur->next;
  29. }
  30. }
  31. return NULL;
  32. }
  33. };

(2)环形链表

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

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * ListNode *next;
  6. * ListNode(int x) : val(x), next(NULL) {}
  7. * };
  8. */
  9. class Solution {
  10. public:
  11. bool hasCycle(ListNode *head) {
  12. if(head != NULL && head->next != NULL){
  13. ListNode* slow = head;
  14. ListNode* fast = head;
  15. while(fast && fast->next){ // 边界问题需要注意
  16. fast = fast->next->next; // fast指针一次走两步
  17. slow = slow->next; // slow指针一次走一步
  18. if(slow == fast){
  19. return true;
  20. }
  21. }
  22. }
  23. return false;
  24. }
  25. };