本题来自142. 环形链表 II - 力扣(LeetCode)
题面:
给定一个链表的头节点 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, 104]
内 -105 <= Node.val <= 105
pos
的值为-1
或者链表中的一个有效索引
进阶:你是否可以使用 O(1)
空间解决此题?
思路:
对于环形链表这一块的题,不能直接遍历了,因为是环啊,没有结尾
所以要考虑一些特殊情况:
1. 空链表
2. 只有一个节点,指向NULL
3. 只有一个节点,指向自己
4. 两个及以上的节点,有环或无环
第4种情况是主要考察点,其他三种情况最好是先用if排除,降低题目难度
具体思路:
对于有环的链表,一开始我以为是这样的(这些线条代表节点)
后来才发现一个巨大的问题,一个节点怎么可能会指向两个节点,只能是两个节点指向一个节点,类似分割链表一样,两个头共用一个尾简单题 相交链表-CSDN博客
也就是说在蓝色的位置只能指向一个地方,要么是环,要么是tail(尾)
很明显,咱们要讨论的是带环链表,上面这种情况是在一个直链的尾上有一个环
还有两种情况,一是整个链表就是一个环,一是一个直链链表的尾指向自己
这两种情况不用单独讨论,只是列举一下可能存在的情况
解决方法:
创建快慢指针(slow,fast),让这两个指针去遍历链表
如果fast指针走到了尾,说明不是带环链表,弹出NULL,
如果fast指针进环,也就意味着fast将一直在环里转圈圈,随着slow的逐渐移动,slow指针也将进环,无论快指针在环的哪一个位置,slow和fast指针一定在环里面相遇,不用去管他们在哪里相遇,只需要知道是在环里面相遇就够了
当slow与fast相遇的时候,说明他俩一定在带环链表的环里面的某个节点,此时有两种方法:
1. 再创建一个指针(start),从head开始遍历,一个指针(find)从slow和fast相遇的位置遍历
让start指针每次移动一个节点,find指针从相遇点转一圈,find指针的条件就是不再回到相遇点
start指针移动的条件就是不和find相同,相同的话就说明找到了啊,弹出该节点
大概意思就是,start从头节点每次移动一个节点,find指针转一圈,find转的过程每次都跟start比较一下,当find回到相遇点停止,start再移动一个节点,find再转一圈……
这种方法就是慢了点,但是能做出来,代码难度不太大,就不放在这里了其实是没写
2. 上帝之手
这个手呢,意思是人为干预一下链表,在之前我们也就找到了slow和fast的相遇点,这个点也在环内,可能在如环的地方,也可能是在环的任意位置,知道在环内就行
然后,最重要的一步就是————人为剪断这个环,我们从相遇点开始断开这个环,怎么个意思呢,就是把相遇的那个节点的next置为NULL
在相遇点断开环
把环掰直,方便理解
掰直了之后发现,这部分环又成了新的直链,不能说是直链吧,就是一个新的头,咱们叫它newhead
我们把原来的链表在相遇点给它断开,将断开位置的下一节点当成新的头,因为相遇点就是slow和fast的所在节点,这里我们让fast指针再向后移动一个节点,slow指针不动,将slow节点的next置空,fast指针指向的就是新的头了newhead(可以为了文章再创建一堆变量,但是做题的时候没必要创建这么多变量)
我们有了新的头了,也就是原来的一个带环链表,被分成了两头一尾的链表
既然是两头一尾,说明至少有一个公共节点,也就是之前做过的题的变形简单题 相交链表-CSDN博客
这道题是让你找环的入口,转化一下就是找两个链表的第一个公共节点嘛
因此借用一下之前做过的题的板子就出来了
代码:
typedef struct ListNode node;
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
// 这个函数是之前写过的函数,不理解的话就去看文章里面的链接,链接就是我之前写过的题的解析,这里就不过多解释了
struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)
{
node* tailA = headA;
node* tailB = headB;
// 获取两个链表的长度
int xA = 0;
int xB = 0;
while (tailA)
{
xA++;
tailA = tailA->next;
}
while (tailB)
{
xB++;
tailB = tailB->next;
}
if (xA > xB)// 对齐头节点
{
while (xA - xB)
{
xA--;
headA = headA->next;
}
}
else
{
while (xB - xA)
{
xB--;
headB = headB->next;
}
}
// 比较节点地址
while (headA && headB)
{
if (headA == headB)
return headA;
headA = headA->next;
headB = headB->next;
}
return NULL;
}
// 这是题给的函数,放在后面是因为咱们自己定义了一个函数,把自定义的函数放前面就不用声明了
struct ListNode* detectCycle(struct ListNode* head)
{
if (!head || !(head->next))//排除空链表和无环的单节点
return NULL;
if (head->next == head)// 排除 指向自己的单节点环
return head;
// 说明不是以上三种情况了
// 快慢指针
node* slow = head;
node* fast = head;
while (fast && fast->next) // 找相遇点
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
break;
}
if (slow != fast) // 看看是不是fast走到尾了,走到尾了说明无环
return NULL;
fast = fast->next; // 让fast后移一步,成为新的头
slow->next = NULL; // 让slow断开,成为尾
return getIntersectionNode(head, fast); // 把原来的头传过去,还有新的头
}
不懂快慢指针的看这个-->简单题 链表的中间节点-CSDN博客