【数据结构 · 初阶】- 带环链表

目录

一.基本结构

二.判断一个单链表带不带环

三.2个问题

[1.为什么 slow 走 1 步,fast 走 2 步,他们会相遇,会不会错过?请证明。](#1.为什么 slow 走 1 步,fast 走 2 步,他们会相遇,会不会错过?请证明。)

[2.为什么 slow 走 1 步,fast 走 X 步 ( X >= 3 ),他们会相遇,会不会错过?请证明。](#2.为什么 slow 走 1 步,fast 走 X 步 ( X >= 3 ),他们会相遇,会不会错过?请证明。)

四.找入环点

法1:结论

法2:转换为求交点


一.基本结构

由图可知,带环链表不能轻易遍例,否则很容易造成死循环

二.判断一个单链表带不带环

141. 环形链表 - 力扣(LeetCode)

思路:快慢指针

都从链表起始位置开始,fast 一次走2步,slow 一次走1步。

如果链表带环则一定会在环中相遇;如果不带环快指针率先走到链表的末尾。

cpp 复制代码
/*Definition for singly-linked list.
struct ListNode
{
    int val;
    struct ListNode *next;
};*/

bool hasCycle(struct ListNode *head)
{
    struct ListNode *fast, *slow;
    fast = slow = head;
    while (fast && fast->next)
    {
        fast = fast->next->next;;
        slow = slow->next;

        if (fast == slow)
        {
            return true;
        }
    }
    return false;
}

三.2个问题

1.为什么 slow 走 1 步,fast 走 2 步,他们会相遇,会不会错过?请证明。

假设链表带环,两个指针最后都会进入环,fast 先进环,slow 后进环。当 fast 到入环第一个节点时,slow 走一半。

假设 slow 进环之后, fast 和 slow 间的距离是 N。fast 开始追击,每次距离减少1。

fast 和 slow 间的距离:N,N-1,N-2,......,2,1,0

当距离为 0 时就追上。

2.为什么 slow 走 1 步,fast 走 X 步 ( X >= 3 ),他们会相遇,会不会错过?请证明。

假设 slow 进环之后, fast 和 slow 间的距离是 N。环的周长是 C

slow 进环之后,fast 开始追击。slow 走1步,fast 走3步。距离每次减 2

fast 和 slow 间的距离:

N偶数:N,N-2,N-4,......,4,2,0 ------ 结束

N奇数:N,N-2,N-4,......,5,3,1,-1 ------ 距离变成 C-1,相当于开启新一轮追击。

若 C-1 是偶数:

fast 和 slow 间的距离:C-1,C-3,......,4,2,0 ------ 结束

若 C-1 是奇数:

fast 和 slow 间的距离:C-1,C-3,......,5,3,1,-1 ------ 距离变成 C-1,又开启新一轮追击。永远追不上。

四.找入环点

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL
142. 环形链表 II - 力扣(LeetCode)

法1:结论

结论:一个指针从相遇点走,另一个从起始点走,每次走1步,会在入环点相遇
证明:

假设:

起始点 - 入环点距离:L

入环点 - 相遇点距离:X ( 0 <= X < C )

环的长度:C

注意:slow 进环,fast 追上 slow 时,slow 还没走满1圈
证明:slow进环后,如果slow走一圈,fast就走了两圈及以上。它们的路程差是一圈,但是它们距离差不可能大于一圈,所以肯定会相遇

slow 走的距离:L + X
fast 走的距离:L + n*C + X( fast 从自己入环到追上 slow 期间,在环里转了 n 圈)( n >= 1)

fast 走的距离是 slow 的2倍

2 * ( L + X ) = L + n*C + X
化简:L = n * C - X 或 L = ( n -1 ) * C + C - X

得证。

cpp 复制代码
/*Definition for singly-linked list.
struct ListNode
{
    int val;
    struct ListNode *next;
};*/

struct ListNode *detectCycle(struct ListNode *head)
{
    struct ListNode *fast, *slow;
    fast = slow = head;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;

        if (slow == fast)
        {
            struct ListNode *meet = slow;
            struct ListNode *start = head;
            
            while (meet != start)
            {
                meet = meet->next;
                start = start->next;
            }
            return meet;
        }
    }
    return NULL;
}

法2:转换为求交点

相遇点与其下一个节点之间的链接断开

找入环点转化为求链表的交点

cpp 复制代码
/*Definition for singly-linked list.
struct ListNode
{
    int val;
    struct ListNode *next;
};*/

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
    struct ListNode *curA = headA, *curB = headB;
    struct ListNode *tailA = headA, *tailB = headB;
    int numA = 1, numB = 1, num = 0; // 遍例链表过程中分别记录2个链表的长度
    while (tailA->next)
    {
        tailA = tailA->next;
        numA++;
    }
    while (tailB->next)
    {
        tailB = tailB->next;
        numB++;
    }
    if (tailA != tailB) // 判断是否有公共节点(本题中这一步可以省略)
        return NULL;

    if (numA > numB) // 让长的链表先走 两链表长度之差 步
    {
        num = numA - numB;
        while (num--)
        {
            curA = curA->next;
        }
    }
    else
    {
        num = numB - numA;
        while (num--)
        {
            curB = curB->next;
        }
    }

    while (curA != curB) // 从短链表位置起遍例,找地址相同的节点,即为交点
    {
        curA = curA->next;
        curB = curB->next;
    }
    return curA;
}

struct ListNode *detectCycle(struct ListNode *head)
{
    struct ListNode *fast, *slow;
    fast = slow = head;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;

        if (slow == fast)
        {
            // 转化为求 lt1  lt2 的交点
            struct ListNode *meet = slow;
            struct ListNode *lt1 = head;
            struct ListNode *lt2 = meet->next;
            meet->next = NULL;
            return getIntersectionNode(lt1, lt2);
        }
    }
    return NULL;
}

meet 指针断开不会影响 getIntersectionNode(lt1, lt2);中 meet 指针的链接。

因为找交点的思路是:

1.判断是否有公共节点(本题中这一步可以省略),遍例链表过程中分别记录2个链表的长度

2.让长的链表先走 两链表长度之差 步

3.从短链表位置起遍例,找地址相同的节点,即为交点

本篇的分享就到这里了,感谢观看 ,如果对你有帮助,别忘了点赞+收藏+关注

小编会以自己学习过程中遇到的问题为素材,持续为您推送文章

相关推荐
CSharp精选营4 天前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
刘马想放假8 天前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
北域码匠9 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
Darling噜啦啦16 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
小小工匠17 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
玖玥拾17 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
Qres82117 天前
算法复键——树状数组
数据结构·算法
牛油果子哥q17 天前
并查集(DSU)超精讲,路径压缩、按秩合并、万能模板、连通性判定、最小生成树与刷题实战全解
数据结构·c++·最小生成树·并查集
凌波粒17 天前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
疯狂成瘾者17 天前
Java 集合 LinkedList 详解:链表结构、常用方法和队列使用
java·开发语言·链表