文章目录
- 01 | 👑 题目描述
- 02 | 🔋 解题思路
- 03 | 🧢 代码片段
The future belongs to those who believe in the beauty of their dreams.
01 | 👑 题目描述
给你一个单链表,请判断其中是否存在环
单链表是一种常见的数据结构,用于存储和组织数据元素。它由一系列节点(Node)组成,每个节点包含一个数据元素和一个指向下一个节点的指针(通常称为next指针)。
单链表的特点是每个节点只能访问其后继节点,而无法直接访问前驱节点或任意位置的节点。链表的头节点即为链表的入口点,尾节点的next指针为空(或指向null),表示到达了链表的末尾。
以下是一个简单的单链表示例:
Head -> Node1 -> Node2 -> Node3 -> ... -> NodeN -> null
在这个示例中,Head
是链表的头节点,Node1
、Node2
、Node3
等分别是链表中的节点,最后的null
表示链表结束。
单链表的插入和删除操作相对灵活,可以在常数时间(O(1))内进行。但是,由于单链表无法直接访问前驱节点,如果需要在链表中查找、删除或修改某个节点,通常需要从头节点开始遍历链表,直到找到目标节点。
单链表具有动态性和节省空间的优势。由于节点在内存中可以不连续存储,因此可以根据需要动态地分配和释放节点空间,有效利用内存资源。
判断单链表是否有环是指判断链表中是否存在一个循环,即链表中某个节点的 next 指针指向之前已经出现过的节点,形成一个环状结构
02 | 🔋 解题思路
常见的判断单链表是否有环的方法是使用快慢指针(双指针)的方法
具体步骤如下:
- 定义两个指针,一个慢指针和一个快指针,初始时都指向链表的头节点。
- 慢指针每次移动一步,快指针每次移动两步。
- 如果链表中不存在环,则快指针会先到达链表的尾部(即指向空节点),此时可以判断链表无环。
- 如果链表中存在环,那么快指针最终会追上慢指针,二者会相遇。
- 如果在遍历过程中出现了相遇,则可以判断链表有环。
原理:
当链表中存在环时,快指针每次移动两步,而慢指针每次移动一步,快指针相对于慢指针的速度差是1步。假设链表中的环有n个节点,当慢指针进入环后,快指针与慢指针之间的距离会逐渐减小,直到最终相遇。而快指针每次移动两步,所以最终一定会追上慢指针。
下面是一个示例:
1 -> 2 -> 3 -> 4 -> 5 -> 2 (存在环)
在这个示例中,链表中的节点2被重复访问,形成了一个环。使用快慢指针法时,快指针会先进入环(比如指向节点4),而慢指针稍后也会进入环。最终,快指针会追上慢指针,二者在节点2相遇。
因此,通过这种方法可以判断一个单链表是否有环。如果两个指针相遇,则链表有环;如果快指针先到达链表尾部(即指向空节点),则链表无环。
时间 && 空间复杂度
- 时间复杂度:O(n),其中 n 是链表中节点的个数
- 快指针每次移动两步,慢指针每次移动一步,当链表中不存在环时,快指针会先到达链表末尾(null),此时需要遍历 n/2 个节点。
- 当链表中存在环时,快指针会在环内循环前进,直到追上慢指针。这个过程最多需要遍历环的长度 k(环中节点的个数)。
- 因此,算法的时间复杂度为 O(n + k),其中 n 是链表中节点的个数,k 是环中节点的个数。
- 空间复杂度:O(1)
因为只使用了常数级别的额外空间来存储慢指针和快指针
03 | 🧢 代码片段
#include <iostream>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
bool hasCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return false; // 空链表或只有一个节点的链表肯定没有环
}
ListNode* slow = head; // 慢指针
ListNode* fast = head; // 快指针
while (fast && fast->next) {
slow = slow->next; // 慢指针每次向后移动一步
fast = fast->next->next; // 快指针每次向后移动两步
if (slow == fast) {
return true; // 快慢指针相遇,说明链表有环
}
}
return false; // 快指针到达链表末尾,说明链表无环
}
int main() {
// 创建一个有环的链表
ListNode* head = new ListNode(1);
ListNode* node2 = new ListNode(2);
ListNode* node3 = new ListNode(3);
ListNode* node4 = new ListNode(4);
ListNode* node5 = new ListNode(5);
head->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = node5;
node5->next = node2; // 环的入口在节点2
// 判断链表是否有环
bool hasCycleFlag = hasCycle(head);
if (hasCycleFlag) {
cout << "链表有环" << endl;
} else {
cout << "链表无环" << endl;
}
// 释放链表内存
delete head;
delete node2;
delete node3;
delete node4;
delete node5;
return 0;
}