链表面试题9之环形链表进阶

那么上一题我们已经知道了双指针的变法以及拓展的用法,那么这里我们直接难度升级。

想回去复习的这里放个链接:链表的面试题8之环形链表-优快云博客

题目链接:142. 环形链表 II - 力扣(LeetCode)

 我们来看这道题目主要想让我们干什么,上一题是让我们去判断是否为环形链表,而这一题是直接告诉你这就是一个环形链表,让你自己去探寻环形链表各节点的关系,用地址来判断这个节点是否是第一个成环的节点。

当然这一题还是用双指针的解法,至于为什么很简单,单指针解决不了这个问题呗。我们上一题探讨过快慢指针的步调的大小,涉及快慢指针的算法题中,通常习惯使用快指针每次走两步,慢指针走一步的方式。

这里我们需要判断的难点是什么??

我们之前用到的快慢指针都是通过他们的距离差让这两个指针相遇来得出这个是环形链表,但是这里我们相遇的点万一不是第一个点呢??那么我们的判断不就成了无效判定了吗?所以我们笃定一点,这道题像上一道题那样的关系判断是不可取的,我们应该找其他的关系,使得让两个节点一定在第一个成环的节点相遇。

那我们直接来画图看一下:

这里我们设从头节点到入环的第一个节点的距离为a,设环长为c。

再设相遇的时候,慢指针走了b步,那么这里快指针就走了2b步。

再设快指针比慢指针多走了k圈环,那么我们就能得到2b-b=kc,即b=kc。

再看我们的慢指针,从头节点开始,到相遇点走了b-a步,也就是(kc-a)步。

这也就是说,从相遇点开始,再走a步,就会到达我们的入环点。

为什么?

因为环长就是c,看图中的箭头,一根箭头代表一个长度。所以再走a步,也就是说(kc-a+a)=kc步

也就是在入环口处。那么哪里还有a步的地方呢?

我再往图里一看,那就是头节点处,所以我如果让指针从头节点和相遇点处分别开始移动,那么两个人步伐都为1的情况下肯定会在入环口处相遇。

结论就是:让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇。

但要注意一点:指针从相遇点开始,移动 a 步后恰好走到入环口,但在这个过程中,可能会多次经过入环口。 因为环长和入环前的长度可能会有误差。

struct ListNode* detectCycle(struct ListNode* head) {
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        if (fast == slow) { // 相遇
            while (slow != head) { // 再走 a 步
                slow = slow->next;
                head = head->next;
            }
            return slow;
        }
    }
    return NULL;
}

这里把代码贴一下,整个思路是非常清晰的,效率也非常高。

时间复杂度:O(n),其中 n 为链表的长度。
空间复杂度:O(1),仅用到若干额外变量。

这里再带大家注意两点推导设条件的前提:1.慢指针在进入环之前,快指针就已经在环内至少走了n圈,而n至少为1。因为快指针至少要走一圈,才能与后进入的慢指针相遇。

2.慢指针进环之后,快指针肯定会在慢指针走一圈之内追上慢指针
因为:慢指针进环后,快慢指针之间的距离最多就是环的长度,而两个指针在移动时,每次它们至今的距离都缩减一步,因此在慢指针移动一圈之前快指针肯定是可以追上慢指针的。

好了,这道题就讲到这里

如果你觉得对你有帮助,可以点赞关注加收藏,感谢您的阅读,我们下一篇文章再见。

一步步来,总会学会的,首先要懂思路,才能有东西写。

<think>好的,用户想了解链表进阶的内容。首先,我需要回顾链表的基础知识,确保用户已经掌握了基本概念,比如节点结构、单链表、双链表等。然后,思考进阶内容可能包括哪些方面。常见的高级链表结构有循环链表、双向循环链表,还有应用场景如LRU缓存、跳表等。此外,算法方面如快慢指针、翻转链表、合并链表等也是重点。 接下来,要考虑用户可能的背景。可能是学生准备面试,或者开发者想深化数据结构知识。需要结构清晰,逐步展开。首先介绍高级链表类型,然后讲常见算法,接着是实际应用,最后提到优化和注意事项。这样用户能有一个系统的认识。 需要注意用户提到的格式要求,所有数学表达式和代码示例要正确使用LaTeX,行内用$...$,独立公式用$$...$$。比如,在解释时间复杂度时,需要用O(n)这样的格式。同时,回答要用中文,保持口语化,避免使用Markdown。 还要检查是否有遗漏的重要知识点,比如哨兵节点、内存管理问题,以及不同语言实现的差异。这些对用户深入理解链表很重要。最后,确保内容真实可靠,例子典型,比如翻转链表和检测环是面试常见题,LRU缓存是实际应用例子。 总结回答结构:先分类介绍进阶链表类型,再讲操作算法,接着应用场景,最后注意事项。中间穿插示例和复杂度分析,帮助用户逐步理解。确保每个部分简明扼要,重点突出,符合用户的学习需求。</think>链表进阶知识可以围绕以下方向展开,我们将逐步解析其核心概念和应用场景: 一、高级链表类型 1. 循环链表 $$ \text{尾结点指针} \rightarrow \text{头结点} $$ • 特点:实现环形数据结构 • 应用:约瑟夫问题、轮询调度 2. 双向链表 $$ \text{结点结构:} [prev|data|next] $$ • 时间复杂度对比: - 插入/删除:$O(1)$(已知前驱) - 单链表则为$O(n)$ 3. 跳表(Skip List) • 空间换时间结构,平均$O(\log n)$的查询效率 • 实现原理:建立多级索引层 二、经典算法操作 1. 快慢指针 ```python # 检测环存在性 slow = fast = head while fast and fast.next: slow = slow.next fast = fast.next.next if slow == fast: return True ``` 2. 链表翻转(递归实现) $$ \text{递归公式:} reverse(head) = reverse(head.next) + \text{连接操作} $$ 3. 合并有序链表 • 关键点:虚拟头结点(dummy node)的使用 三、典型应用场景 1. LRU缓存淘汰算法 • 使用双向链表+哈希表实现$O(1)$操作 • 最近访问的节点移动到链表头部 2. 多项式表示 • 每个结点存储$ax^n$项 • 实现多项式加法的时间复杂度$O(m+n)$ 四、优化与注意事项 1. 哨兵节点技巧 • 减少边界条件判断 • 示例代码: ```python dummy = ListNode(0) dummy.next = head ``` 2. 内存管理 • C/C++需注意内存释放 • Java/Python依赖GC但要注意对象引用 3. 工程实践差异 • Linux内核链表实现:侵入式设计 • Redis列表:双向链表+压缩列表优化 理解这些进阶内容后,建议通过LeetCode典型题目巩固: - 25. K个一组翻转链表 - 146. LRU缓存机制 - 141/142. 环形链表系列 - 23. 合并K个升序链表 实际开发中,链表常用于需要频繁插入删除的场景,但要注意其缓存不友好的特性,在性能敏感场景可考虑数组或混合结构替代方案。
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值