写点什么

算法题学习 --- 链表中环的入口结点

作者:桑榆
  • 2022-11-11
    广东
  • 本文字数:2014 字

    阅读完需:约 7 分钟

题目

描述

给一个长度为 n 链表,若其中包含环,请找出该链表的环的入口结点,否则,返回 null。

数据范围: n≤10000,1<=结点值<=10000

要求:空间复杂度 O(1),时间复杂度 O(n)

例如,输入{1,2},{3,4,5}时,对应的环形链表如下图所示:

可以看到环的入口结点的结点值为 3,所以返回结点值为 3 的结点。

输入描述:

输入分为 2 段,第一段是入环前的链表部分,第二段是链表环的部分,后台会根据第二段是否为空将这两段组装成一个无环或者有环单链表

返回值描述:

返回链表的环的入口结点即可,我们后台程序会打印这个结点对应的结点值;若没有,则返回对应编程语言的空结点即可。

示例 1

输入:{1,2},{3,4,5}返回值:3说明:返回环形链表入口结点,我们后台程序会打印该环形链表入口结点对应的结点值,即3   
复制代码

示例 2

输入:{1},{}返回值:"null"说明:没有环,返回对应编程语言的空结点,后台程序会打印"null"   
复制代码

示例 3

输入:{},{2}返回值:2说明:环的部分只有一个结点,所以返回该环形链表入口结点,后台程序打印该结点对应的结点值,即2  
复制代码

分析

考虑特殊情况

在进行问题分析过程前,我们先考虑一下特殊情况,那么在这道问题中,有哪些情况是特殊的呢?

不难发现,如下情况:

  • 如果输入的头结点为空,那么返回 nullptr 即可

  • 如果输入的头结点的 next 为空,那么也同样返回 nullptr 即可。

考虑一般情况

很容易想到第一种方法

我们从头节点开始遍历链表,并使用 set 记录每个节点的指针,如果没有发现相同的就 insert 到 set 中,如果发现有相同的,那就说明出现了重复的指针,说明此时就是环的起点,返回该指针即可

很明显,这种方法在最坏的情况下面,会占用 O(n)的空间,并不满足题目的要求,基于此,我们实现的是版本一的代码。

第二种方法

第二种方法,我们就要用到前面使用过的快慢双指针的方法,当快慢双指针相等时,即说明慢指针追上了快指针,说明有环,此时我们来分析一下此时的位置情况,如下

我们假设从头节点到环入口的距离为 x,环入口到相遇点的距离为 y,相遇点到环入口的距离为 z,则环的长度为 y+z,同时我们假设慢指针已经在环内走了 m 圈,快指针走了 n 圈,则有

快指针走的步数=x+y+n(y+z);

慢指针走的步数=x+y+m(y+z);

同时我们又知道,快指针走的步数是慢指针的两倍,即 x+y+n(y+z) = 2(x+y+m(y+z)),化简一下可得:

x+y = (n-2m)(y+z),x+y 正好是 y+z 的整数倍,那么当我们使用两个指针,一个从头结点出发,一个从当前相遇的节点出发,那么他们一定会相遇而且有一段距离 y 一点是会相互重合的,它们相遇的第一个节点就应该是环的入口了。

所以步骤如下:

  • step 1:使用之前的方法判断链表是否有环,并找到相遇的节点。

  • step 2:慢指针继续在相遇节点,快指针回到链表头,两个指针同步逐个元素逐个元素开始遍历链表。

  • step 3:再次相遇的地方就是环的入口。

代码示例

版本一

思路比较清晰,简洁,但是空间复杂度比较大

/*struct ListNode {    int val;    struct ListNode *next;    ListNode(int x) :        val(x), next(NULL) {    }};*/class Solution {public:    ListNode* EntryNodeOfLoop(ListNode* pHead) {        if(nullptr == pHead || nullptr == pHead->next){            return nullptr;        }
std::set<ListNode*> walkedListNode; ListNode* pReturn = nullptr; ListNode* pCur = pHead; while(nullptr != pCur){ if(walkedListNode.count(pCur) == 1){ pReturn = pCur; break; }else{ walkedListNode.insert(pCur); pCur = pCur->next; } } return pReturn; }};
复制代码

版本二

较难理解,特别是找到相遇点后,快指针又退回头结点,再次遍历,这里需要计算,可以结合图自己分析一下。

/*struct ListNode {    int val;    struct ListNode *next;    ListNode(int x) :        val(x), next(NULL) {    }};*/class Solution {public:    ListNode* EntryNodeOfLoop(ListNode* pHead) {        if(nullptr == pHead || nullptr == pHead->next){            return nullptr;        }
ListNode* pQuick = pHead; ListNode* pSlow = pHead; ListNode* pReturn = nullptr; bool hasLoop = false; while(nullptr != pQuick && nullptr != pQuick->next){ pQuick = pQuick->next->next; pSlow = pSlow->next; if(pQuick == pSlow){ hasLoop = true; break; } } if(hasLoop){ pQuick = pHead; while(pQuick != pSlow){ pQuick = pQuick->next; pSlow = pSlow->next; } pReturn = pQuick; } return pReturn; }};
复制代码


发布于: 刚刚阅读数: 4
用户头像

桑榆

关注

北海虽赊,扶摇可接;东隅已逝,桑榆非晚! 2020-02-29 加入

Android手机厂商-相机软件系统工程师 爬山/徒步/Coding

评论

发布
暂无评论
算法题学习---链表中环的入口结点_算法题_桑榆_InfoQ写作社区