中等题 环形链表(妙解)

本题来自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博客

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值