首页 > 其他分享 >山田凉带你玩转OJ--判断链表是否有环并返回环的起始结点

山田凉带你玩转OJ--判断链表是否有环并返回环的起始结点

时间:2024-11-12 18:46:08浏览次数:3  
标签:山田 结点 ListNode OJ next 链表 NULL struct

在这里插入图片描述

技术博客:判断链表是否有环并返回环的起始结点

引言

在链表操作中,判断链表是否存在环形结构是一个常见的问题。本文将详细介绍如何使用快慢指针法判断链表是否有环,并进一步找到环的起始结点。我们将分步骤讲解每一步的实现原理,并提供完整的代码实现。

1. 题目解读

题目要求:判断一个链表中是否存在环形结构,并返回环形结构的起始结点。

链表节点结构定义如下:

struct ListNode {
    int val;
    struct ListNode *next;
};

2. 解题思路

2.1 判断链表是否有环

使用快慢指针法判断链表是否有环。快指针 fast 每次移动两步,慢指针 slow 每次移动一步。如果存在环,快指针和慢指针最终会在环内相遇。

2.2 寻找环的起始结点

一旦确定链表存在环,我们需要找到环的起始结点。假设链表的头节点到环的起始结点的距离为 a,环的长度为 c,快指针和慢指针在环内的某个点相遇,慢指针已经走了 a + b 步,快指针已经走了 a + b + k * c 步(其中 k 是快指针在环内绕的圈数)。

根据快慢指针的速度关系,可以得出以下方程:

[ 2(a + b) = a + b + k / c ]

简化方程:

[ a + b = k /c ]

从第一个方程可以推出:

[ a + b = k / c ]

从第二个方程可以推出:

[ a = (k - 1) / c + (c - b) ]

这表明从头节点开始走 a 步和从相遇点开始走 c - b 步会到达同一个位置,即环的起始结点。

3. 具体代码实现

3.1 判断链表是否有环

struct ListNode* hasCycle(struct ListNode* head) {
    if (head == NULL || head->next == NULL) {
        return NULL;
    }

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

    while (fast != NULL && fast->next != NULL) {
        slow = slow->next;
        fast = fast->next->next;

        if (slow == fast) {
            return slow;  // 相遇点
        }
    }

    return NULL;  // 无环
}

详细解释

  • 初始条件slowfast 都指向链表的头部。
  • 移动条件fast 每次移动两步,slow 每次移动一步。
  • 终止条件:如果 fast 到达链表末尾(fast == NULLfast->next == NULL),则链表无环。如果 slowfast 相遇,则链表有环。

3.2 寻找环的起始结点

struct ListNode* detectCycle(struct ListNode* head) {
    struct ListNode *meetNode = hasCycle(head);
    if (meetNode == NULL) {
        return NULL;  // 无环
    }

    struct ListNode *startNode = head;
    struct ListNode *currentNode = meetNode;

    while (startNode != currentNode) {
        startNode = startNode->next;
        currentNode = currentNode->next;
    }

    return startNode;  // 环的起始结点
}

详细解释

  • 初始条件startNode 指向链表头部,currentNode 指向相遇点。
  • 移动条件startNodecurrentNode 每次各移动一步。
  • 终止条件:当 startNodecurrentNode 相遇时,返回 startNode,即环的起始结点。
3.3 完整代码
#include <stdio.h>

// 定义链表节点结构
struct ListNode {
    int val;
    struct ListNode *next;
};

// 判断链表是否有环
struct ListNode* hasCycle(struct ListNode* head) {
    if (head == NULL || head->next == NULL) {
        return NULL;
    }

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

    while (fast != NULL && fast->next != NULL) {
        slow = slow->next;
        fast = fast->next->next;

        if (slow == fast) {
            return slow;  // 相遇点
        }
    }

    return NULL;  // 无环
}

// 寻找环的起始结点
struct ListNode* detectCycle(struct ListNode* head) {
    struct ListNode *meetNode = hasCycle(head);
    if (meetNode == NULL) {
        return NULL;  // 无环
    }

    struct ListNode *startNode = head;
    struct ListNode *currentNode = meetNode;

    while (startNode != currentNode) {
        startNode = startNode->next;
        currentNode = currentNode->next;
    }

    return startNode;  // 环的起始结点
}

// 测试函数
int main() {
    // 创建测试链表 1 -> 2 -> 3 -> 4 -> 5 -> 3 (环)
    struct ListNode *node1 = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode *node2 = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode *node3 = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode *node4 = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode *node5 = (struct ListNode*)malloc(sizeof(struct ListNode));

    node1->val = 1;
    node1->next = node2;
    node2->val = 2;
    node2->next = node3;
    node3->val = 3;
    node3->next = node4;
    node4->val = 4;
    node4->next = node5;
    node5->val = 5;
    node5->next = node3;  // 形成环

    struct ListNode *cycleStart = detectCycle(node1);
    if (cycleStart != NULL) {
        printf("环的起始结点值为: %d\n", cycleStart->val);
    } else {
        printf("链表无环\n");
    }

    // 释放内存
    free(node1);
    free(node2);
    free(node3);
    free(node4);
    free(node5);

    return 0;
}

4. 注意事项

  • 边界条件
    • 如果链表为空或只有一个节点,直接返回 NULL
    • 如果链表无环,返回 NULL
  • 内存管理
    • 确保在测试完成后释放动态分配的内存,避免内存泄漏。

5. 寻找环起始节点的数学方法详解

5.1 快慢指针相遇点的数学推导

假设链表的头节点到环的起始结点的距离为 a,环的长度为 c,快指针和慢指针在环内的某个点相遇,慢指针已经走了 a + b 步,快指针已经走了 a + b + k * c 步(其中 k 是快指针在环内绕的圈数)。

根据快慢指针的速度关系,可以得出以下方程:

[ 2(a + b) = a + b + k / c ]

简化方程:

[ a + b = k / c ]

从第一个方程可以推出:

[ a + b = k / c ]

从第二个方程可以推出:

[ a = (k - 1) / c + (c - b) ]

这表明从头节点开始走 a 步和从相遇点开始走 c - b 步会到达同一个位置,即环的起始结点。

5.2 为什么从头节点和相遇点同时移动会相遇在环的起始结点

假设从头节点开始走 a 步到达环的起始结点,从相遇点开始走 c - b 步也会到达环的起始结点。因为 ac - b 的总和都是环的长度 c 的整数倍加上从环的起始结点到相遇点的距离 b

因此,从头节点和相遇点同时移动,每次各移动一步,最终会在环的起始结点相遇。

6. 总结

本文详细介绍了如何判断一个链表中是否存在环形结构,并找到环的起始结点。通过使用快慢指针法和数学分析,我们可以高效地实现这一功能。希望这篇博客对你有所帮助!如果有任何问题或需要进一步的解释,请随时提问。
在这里插入图片描述

标签:山田,结点,ListNode,OJ,next,链表,NULL,struct
From: https://blog.csdn.net/PSL060309/article/details/143494649

相关文章

  • OJ08题:876. 链表的中间结点
    目录题目思路分析代码展示题目给你单链表的头结点head,请你找出并返回链表的中间结点。注:如果有两个中间结点,则返回第二个中间结点。示例1:输入:head=[1,2,3,4,5]输出:[3,4,5]解释:链表只有一个中间结点,值为3。示例2:输入:head=[1,2,3,4,5,6]输出:[4,5......
  • 代码随想录算法训练营第三天(LeetCode203.移除链表元素;LeetCode707.设计链表;LeetCode20
    LeetCode203.移除链表元素题目链接:LeetCode203.移除链表元素题目链接思路这道题目主要考察的是移除一个链表当中的元素,我们可以先在给定的链表前面加一个虚拟头结点,这样我们对给定链表头结点的操作和给定链表其余结点的操作就会变得相同。代码classSolution{p......
  • 代码随想录算法训练营第四天(LeetCode24.两两交换链表中的节点;LeetCode10.删除链表的倒
    LeetCode24.两两交换链表中的节点题目链接:两两交换链表中的节点题目链接思路这道题其实就是一个模拟题,要求每次交换链表中两个相邻的节点(1、2节点互换;3、4节点互换;2、3节点不互换,意思就是交换过的节点不参与后续的交换了),同时只能进行节点交换,不能进行值交换。主要考......
  • 【初阶数据结构与算法】线性表之链表的分类以及双链表的定义与实现
    文章目录一、链表的分类二、双链表的实现1.双链表结构的定义2.双链表的初始化和销毁初始化函数1初始化函数2销毁函数3.双链表的打印以及节点的申请打印函数节点的申请4.双链表的头插和尾插头插函数尾插函数5.双链表的查找和判空查找函数判空函数6.双链表的头删和尾......
  • Mysql篇-Buffer Pool中的三大链表
    为什么要有BufferPool?虽然说MySQL的数据是存储在磁盘里的,但是也不能每次都从磁盘里面读取数据,这样性能是极差的。要想提升查询性能,那就加个缓存。所以,当数据从磁盘中取出后,缓存内存中,下次查询同样的数据的时候,直接从内存中读取。为此,Innodb存储引擎设计了一个缓冲池(Buffer......
  • 计算二叉树(二叉链表)的带权路径长度
    方法1#include<bits/stdc++.h>#defineintlonglong#definemod998244353usingnamespacestd;usingpii=pair<int,int>;typedefstructBtnode{ intdata; structBtnode*lc,*rc; }*Btree,Btnode;//笔试这个可以放上面,但是真的写程序应该把getwpl放在g......
  • AcWing 1626:链表元素分类 ← 单链表
    【题目来源】https://www.acwing.com/problem/content/1628/【题目描述】给定一个单链表,请编写程序将链表元素进行分类排列,使得所有负值元素都排在非负值元素的前面,而[0,K]区间内的元素都排在大于K的元素前面。但每一类内部元素的顺序是不能改变的。例如:给定链表为18→......
  • P10592 BZOJ4361 isn
    P10592BZOJ4361isn当一个序列删成非降序列的话那操作就要停止,所以我们要求的是最后一步刚好删成非降序列的操作数,但是这样做太复杂了,我们先不考虑停止操作,让他一直删下去。这时我们就要知道长度为\(i\)的非降序列的数量然后才能计算答案,我们有\(f_{i,j}\)为第\(i\)个数......
  • 随机链表 (Randomized Linked List)、随机树 (Randomized Tree)详细解读
    一、随机化数据结构(RandomizedDataStructures)随机化数据结构是通过引入随机性来优化传统数据结构的性能,特别是在最坏情况性能表现较差时。通过随机化,许多原本具有较差时间复杂度的操作可以实现平均O(1)或O(logn)的时间复杂度,减少了最坏情况下的复杂度。常见的随机......
  • 【模板】如何实现链表元素的反转
    反转链表是链表操作中一个经典的问题,也是面试中常见的考题。本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作。我们将使用C++代码演示具体实现,同时分析时间复杂度和空间复杂度。问题定义给定一个单链表,我们需要将链表的节点顺序反转。例如,链表1......