Leetcode 31

1 题目

142. 环形链表 II

给定一个链表的头节点 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 或者链表中的一个有效索引

2 思路

实际上这是141. 环形链表 - 力扣(LeetCode)的进阶版

1 首先判断是否有环(可以按照easy的策略,快慢指针相遇)

2 返回环节点,按照图中的路径,其实是一点数学呢,相遇以后,其中一个指针到头节点,快慢指针以相同速度前进,再次项羽点就是环节点

3 注意非空,注意临界条件

3 代码实现(c)

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
    ListNode *one = head ;
    ListNode *two = head;
    while(one != NULL && one-> next != NULL){
        one = one -> next -> next ;
        two = two -> next ;
        if(one == two){
            two = head ;
            while(one != two){
                one = one -> next;
                two = two -> next;
                } 
             return one ;   
            }
        } 
           return NULL;
    }

    

初版,失败的代码()

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
    ListNode *one = head->next;
    ListNode *two = head;
    while(one != NULL){
        one = one -> next ;//一开始是想让one,two做两个小跟班,实际上逻辑行不通
        two = two -> next ;
        if(one == two){
            two = head ;
            while(one != two){
                one = one -> next;
                two = two -> next;
                if(one == two){
                    break;
                } 
            }
            return one;
        }
    }
    return NULL;
    
}

代码中的问题

  1. 初始指针位置错误 代码中初始化为 one = head->next; two = head;,若链表为空(head == NULL),会直接访问 head->next 导致空指针异常。

  2. 快慢指针步长错误 检测环时,快指针应每次走 2 步,慢指针每次走 1 步(才能利用 "相对速度" 相遇)。但代码中 one(快指针)和 two(慢指针)每次都只走 1 步(one = one->next; two = two->next;),相当于两个指针同速前进,若有环也可能永远不相遇。

正确的环起始节点检测逻辑

要找到环的起始节点,核心利用快慢指针相遇的性质

  1. 检测环是否存在 :快指针(每次 2 步)和慢指针(每次 1 步)从表头出发,若相遇则有环;若快指针走到终点(NULL)则无环。

  2. 找到环的起始节点:当快慢指针相遇时,将其中一个指针重置到表头,两者以相同速度(每次 1 步)前进,再次相遇的节点就是环的起始节点。

    原理 :设表头到环起点距离为 a,环起点到相遇点距离为 b,环长度为 c。相遇时快指针走了 a + b + k*ck 为圈数),慢指针走了 a + b。由于快指针速度是慢指针的 2 倍,因此 a + b + k*c = 2*(a + b),化简得 a = k*c - b,即表头到环起点的距离 = 相遇点到环起点的距离(绕环 k-1 圈后)。因此两指针同速前进时,会在环起点相遇。

修正后的代码(示例)

cpp 复制代码
struct ListNode *detectCycle(struct ListNode *head) {
    if (head == NULL || head->next == NULL) {
        return NULL; // 空链表或只有一个节点,无环
    }

    ListNode *slow = head;
    ListNode *fast = head;

    // 第一步:检测环是否存在(快慢指针相遇)
    while (fast != NULL && fast->next != NULL) {
        slow = slow->next;       // 慢指针走1步
        fast = fast->next->next; // 快指针走2步
        if (slow == fast) {      // 相遇,有环
            // 第二步:找环的起始节点
            slow = head;         // 慢指针重置到表头
            while (slow != fast) {
                slow = slow->next;
                fast = fast->next; // 同速前进,再次相遇即为环起点
            }
            return slow;
        }
    }

    return NULL; // 无环
}

总结

  • 原代码因指针初始位置和步长错误,无法正确检测环和起始节点。
  • 正确逻辑需利用快慢指针的 "2 倍速差" 相遇检测环,再通过重置指针同速前进找到环起点。
相关推荐
Miqiuha3 小时前
观察者模式学习
学习·观察者模式
百锦再4 小时前
破茧成蝶:全方位解析Java学习难点与征服之路
java·python·学习·struts·kafka·maven·intellij-idea
雨奔4 小时前
Django 学习路线图
学习·django·sqlite
吃着火锅x唱着歌4 小时前
LeetCode 74.搜索二维矩阵
算法·leetcode·矩阵
mit6.8244 小时前
hash|快速幂|栈
算法
OG one.Z4 小时前
06_决策树
算法·决策树·机器学习
爪哇部落算法小助手5 小时前
每日两题day23
算法
妮妮喔妮5 小时前
10.25复习LRU缓存[特殊字符]
算法
冷崖5 小时前
MySQL-TrinityCore异步连接池的学习(七)
学习·mysql