LeetCode 热题 100_环形链表 II(26_142)
题目描述:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置**(索引从 0 开始)**。如果 pos 是 -1,则在该链表中没有环。注意 :pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
输入输出样例:
示例 1:
输入 :head = [3,2,0,-4], pos = 1
输出 :返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入 :head = [1,2], pos = 0
输出 :返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入 :head = [1], pos = -1
输出 :返回 null
解释:链表中没有环。
提示:
链表中节点的数目范围在范围 [0, 10^4^] 内
-10^5^ <= Node.val <= 10^5^
pos 的值为 -1 或者链表中的一个有效索引
题解:
解题思路:
思路一(哈希表) :
1、创建一个哈希集合用于存储遍历过的链表结点,如果当前遍历的结点不在哈希集合中则存入集合,如果当前遍历的结点存在哈希集合中则为链表开始入环的第一个节点,返回该结点程序结束。
2、复杂度分析:
① 时间复杂度:O(N),其中 N 为链表中节点的数目。我们恰好需要访问链表中的每一个节点。
② 空间复杂度:O(N),其中 N 为链表中节点的数目。我们需要将链表中的每个节点都保存在哈希表当中。
思路二(快慢指针):
1、通过题目分析,我们需要找到链表开始入环的第一个节点,通过分析我们可以得出下图(a代表环状链表之前的节点数,b代表环装链表的节点数):
fast=2slow(每次fast走两步,slow走一步)
fast=slow+n b (b代表环中结点的个数,nb代表slow==fast时,fast比slow多在环中转了多少圈)
两式联立可得fast=2nb,slow=n*b。
如果我们从头开始遍历结点到环的入口结点需要走:a+mb步。m取值为(0,1,2,...)
我们发现 slow=n b 再走 a 步,也就是 a+n*b正好满足到达入口结点的条件。
我们这里的a的大小不知道怎么办,我们发现从首结点开始到入口正好为a步,所以我们从head和slow(fast==slow时)同时移动则会在环形入口处相遇。
2、具体思路如下:
① 定义两个指针,fast和slow,fast每次走两步,slow每次走一步。
② 通过遍历找到找到环中slow==fast的点(如果fast遍历到nullptr则不存在环,也就不存在环的入口结点)
③ 然后再令fast指向首结点,将fast和slow同时进行移动,这时fast每次移动一步,直到slow ==fast此时指向的结点为环的入口结点。
3、复杂度分析
① 时间复杂度:O(N),第二次相遇中,慢指针须走步数 a<a+b;第一次相遇中,慢指针须走步数 a+b−x<a+b,其中 x 为双指针重合点与环入口距离;因此总体为线性复杂度;
② 空间复杂度:O(1),双指针使用常数大小的额外空间。
代码实现(思路一(哈希表)):
cpp
ListNode *detectCycle1(ListNode *head) {
unordered_set<ListNode*> mp_set;
//创建一个哈希集合用于存储遍历过的链表结点,如果当前遍历的结点不在哈希集合中则存入集合,如果前遍历的结点存在哈希集合中(第一个)则为链表开始入环的第一个节点,返回该结点程序结束。
while(head!=nullptr){
if(mp_set.count(head)){
return head;
}else{
mp_set.insert(head);
head=head->next;
}
}
return nullptr;
}
代码实现(思路二(快慢指针)):
cpp
ListNode *detectCycle2(ListNode *head) {
ListNode *slow=head,*fast=head;
//如果是无环的情况便返回,存在环则slow=fast结束循环
while(true){
if(fast==nullptr||fast->next==nullptr){
return nullptr;
}
fast=fast->next->next;
slow=slow->next;
if(fast==slow){
break;
}
}
//将fast更改为首节点位置,这里fast和slow移动一步,相遇节点为开始入环的第一个节点
fast=head;
while(fast!=slow){
fast=fast->next;
slow=slow->next;
}
return fast;
}
以思路二为例完成代码调试
cpp
#include<iostream>
#include<vector>
#include<unordered_set>
using namespace std;
struct ListNode{
int val;
ListNode *next;
ListNode(int x):val(x),next(nullptr){}
};
//尾插法创建单链表
ListNode* createList(const vector<int> &a){
ListNode *head=nullptr,*tail=nullptr;
for(const auto &val:a){
ListNode *newNode=new ListNode(val);
if(head==nullptr){
head=newNode;
tail=newNode;
}else{
tail->next=newNode;
tail=newNode;
}
}
return head;
}
//将单链表变成环形单链表(这里只是末尾存在环的情况)
ListNode* createCircularList(ListNode *head,int pos){
//无环则返回
if(pos==-1){
return head;
}
ListNode *posNode=head;
//找到环的入口
while(pos){
posNode=posNode->next;
--pos;
}
//找到链表的末尾
ListNode *tail=posNode;
while(tail->next!=nullptr){
tail=tail->next;
}
//想成环
tail->next=posNode;
return head;
}
//方法二(快慢指针)
ListNode *detectCycle2(ListNode *head) {
ListNode *slow=head,*fast=head;
//如果是无环的情况便返回,存在环则slow=fast结束循环
while(true){
if(fast==nullptr||fast->next==nullptr){
return nullptr;
}
fast=fast->next->next;
slow=slow->next;
if(fast==slow){
break;
}
}
//将fast更改为首节点位置,这里fast和slow移动一步,相遇节点为开始入环的第一个节点
fast=head;
while(fast!=slow){
fast=fast->next;
slow=slow->next;
}
return fast;
}
//主函数
int main(){
vector<int> a={3,2,0,-4};
int pos=1;
ListNode *head=createList(a);
ListNode *cir_head=createCircularList(head,pos);
ListNode *ans=detectCycle2(cir_head);
if(ans!=nullptr){
cout<<"链表开始入环的第一个结点值为:"<<ans->val;
}else{
cout<<"链表没有开始入环的第一个结点";
}
return 0;
}
unordered_map和unordered_set对比及用法请点击此链接
ListNode(int x):val(x),next(nullptr){}解读请点击此链接
LeetCode 热题 100_环形链表 II(26_142)原题链接
欢迎大家和我沟通交流(✿◠‿◠)