美文网首页基础算法分析实现
单链表有环问题 - 你emo了没

单链表有环问题 - 你emo了没

作者: erlich | 来源:发表于2022-08-10 19:13 被阅读0次

这是个老套的问题,也是个经典的问题

有没有技术含量,我们不纠结

从最朴实的逻辑思路去分析,然后自己感觉为什么总有人要问及这个问题

...

问题:

  • 判断一个单链表是否有环?
  • 如果有环存在,找出环的入口节点

要求:

  • 空间复杂度为O(1) 也就是不能有额外的存储空间

示意图

image.png

分析

我们最直观的意识,可能就是用set遍历存储节点,由于set不能存储重复元素,所以只要在某个节点存储时,发现set中已经包含元素,则说明有环存在

但是题目要求不能额外开辟空间

剩下的条件很有限,像这样很明确的很简单的条件,往往我们考虑起来却花费很长时间,往往还想不明白,明明感觉很简单的

几何定理也只是通过5条很简单的公理推导出来的

有这种困惑,原因在于我们平时缺乏这方面的思维锻炼

思路方向的诊断

  • 首先,判断单链表的状态,一定离不开链表本身的特性,也一定会用到,就是遍历,这点不用怀疑
  • 判断环存在,就是在遍历过程中,需要判断是否有重复结点
  • 依靠一个遍历指针是做不到的,需要两个指针,才能完成比较
  • 判断环,就是两个指针遍历过程中是否相遇,遍历一定次数结束,结束时判断两个指针是否指向同一个节点
  • 两个指针要相遇,必然龟兔赛跑绕圈追赶的情形,这就要求两个指针的遍历步长不能是一样的,必然存在一个快,一个慢
  • 快指针步长两个节点,慢指针步长1个节点
    • 可否快指针步长3个节点,也不是不可以,但执行代码就稍微麻烦些,每次需要判断next.next是否null

手代替脑活动

情况一:正好在环入口相遇

image.png

情况一:非环入口节点相遇,在环内的一个节点相遇

image.png
  • 快指针pf,慢指针ps

  • 指针第一次相遇时,快指针遍历过的节点数f, 慢指针遍历过的节点数s

  • 慢指针每遍历1个节点,快指针遍历两个结点

    • f = 2s
  • 由示意图得知,快指针与慢指针存在绕圈追赶的情形

    • 圈中的节点数: t, 两指针第一次相遇时 绕过的圈数: n
    • 快指针遍历的节点数比 慢指针 快n圈,也就是 多遍历n * t个节点
    • f = s + n * t = 2s
    • 得到 s = n * t; f = 2n * t
    • 两指针相遇时,慢指针遍历的节点 个数 为环中节点数的 n倍,快指针2n
  • 情形一: f = a1 + m1 * t (m1代表 m1圈)

    情形二: f = a2 + m1 * t

  • 由情形一 m1 * t + a1,到达环形入口,也就从链表头遍历m1 * t 个节点,在此节点位置再遍历a1个节点,达到入口

    现在就可以得出情形一: a1的长度 就是 环的长度

  • 由情形二 m1 * t + a2, 到达相遇节点(环中的一个节点,非入口节点), 也就从链表头遍历m1 * t 个节点,在此节点位置再遍历a2个节点,到达相遇节点

    a2的长度 也等于 环的长度

  • 当快指针跟慢指针首次相遇时

    • 让快指针重新指向head,步长为1开始遍历

    • 慢指针从相遇节点开始遍历

    • 这时候 快指针跟慢指针 是同长遍历的,不存在快慢的问题

    • 快指针跟慢指针再次首次相遇时,相遇的节点就是 环的入口节点了

      因为两种情形,绕一圈会在相遇节点再次相遇,但其实,在这之前,有一段遍历是重叠的,就是入口节点到相遇节点这一段

到此,分析结束,实现代码就不存在任何障碍了

实现

struct LinkList {
    int data;
    struct LinkList *next;
};

// 创建 nodeCount个节点的 单链表
struct LinkList* createLinkList(int nodeCount) {
    struct LinkList *head;
    struct LinkList *p;
    for (int i = 0; i < nodeCount; i++) {
        struct LinkList* node = (struct LinkList *)malloc(
sizeof(struct LinkList));
        node->data = i + 1;
        node->next = NULL;
        if (i == 0) {
            head = node;
            p = node;
        } else {
            p->next = node;
            p = node;
        }
    }
    return head;
}

// 打印单链表
// 最多打印 maxNodeCount 个节点
void printLinkList(struct LinkList *linkList, int maxNodeCount) {
    int index = 0;
    struct LinkList *p = linkList;
    if (p == NULL) {
        return;
    }
    while (p != NULL) {
        if (index >= maxNodeCount) {
            break;
        }
        printf("-->%i", p->data);
        p = p->next;
        index++;
    }
    

    printf("\n");
}

// 单链表构建一个环
// nodeIndex 第nodeIndex个节点 作为环入口
void configLinkCircle(struct LinkList *linkList, int nodeIndex) {
    int index = 0;
    struct LinkList *entraceNode = NULL;
    struct LinkList *p = linkList;
    if (p == NULL) {
        return;
    }
    while (p != NULL) {
        if (index == nodeIndex) {
            entraceNode = p;
        }
        if (p->next == NULL) {
            p->next = entraceNode;
            break;
        }
        p = p->next;
        index++;
    }
}

// 判断环 返回入口节点
struct LinkList *checkLinkListCircle(struct LinkList *linkList) {
    // 链表头
    struct LinkList *head = linkList;
    // 快指针
    struct LinkList *pf = linkList;
    // 慢指针
    struct LinkList *ps = linkList;
    if (linkList == NULL) {
        return NULL;
    }
    
    while (true) {
        if (pf != NULL && pf->next != NULL) {
            ps = ps->next;
            pf = pf->next->next;
            if (pf == ps) {
                break;
            }
        } else {
            printf("\n链表不存在环\n");
            return NULL;
        }
    }
    
    pf = head;
    int index = 0;
    while (pf != ps) {
        index++;
        ps = ps->next;
        pf = pf->next;
    }
    index++;
    printf("\n链表在第%i个节点处形成环,节点为:%i\n", index + 1, pf->data);
    
    return NULL;
}


int main(int argc, const char * argv[]) {
    int n = 30;
    
    int nodeCount = 20;
    struct LinkList *linkList = createLinkList(nodeCount);
    configLinkCircle(linkList, 8);
    printLinkList(linkList, nodeCount * 2);
    
    struct LinkList* entraceNode = checkLinkListCircle(linkList);
    
    return 0;
}
  • 单链表是否存在环的问题一点也不复杂,但往往让人困惑的地方在于简单的数学公式推导,靠脑子抽象往往不会任何输出

相关文章

  • 单链表有环问题 - 你emo了没

    这是个老套的问题,也是个经典的问题 有没有技术含量,我们不纠结 从最朴实的逻辑思路去分析,然后自己感觉为什么总有人...

  • 链表算法面试?看我就够了!——链表中存在环问题

    链表中存在环问题 3.1 判断链表是否有环 单链表中的环是指链表末尾的节点的 next 指针不为 NULL ,而是...

  • 检测链表有环

    题目:如何判断一个单链表是否有环?若有环,如何找出环的入口节点。 一、单链表是否有环 思路分析: 单链表有环,是指...

  • 判断单链表是否有环、求环长和环入口最优算法

    判断单链表是否有环、求环长和环入口最优算法 判断单链表是否有环是一个十分经典的算法问题,许多考试或者面试都有很大的...

  • 有环单链表的相关问题

    问题:判断单链表是否有环;若有环,找出环的入口节点;若有环,求出环上节点的个数;若有环,求出整个链表的节点的个数;...

  • 2020-08-19

    单链表找环(floyd算法) 首先是示意图,链表中有环就是这种情况 问题是,在这样一个单链表中,若有环,寻找出环的...

  • 数据结构与算法之链表(四)单链表尾环问题全面分析

    引言 单链表的尾环问题是一个比较经典的问题,它涉及的问题如下:1>给一个单链表,判断其中是否有环的存在;2>如果存...

  • 单链表是否存在环

    面试题:如何检测一个单链表是否有环?如果有环的入口在哪里? 题目描述: 单链表有环指的是单链表中某个结点的next...

  • 链表

    现在有一个单向链表,谈一谈,如何判断链表中是否出现了环 考察点:链表参考回答:单链表有环,是指单链表中某个节点的n...

  • 单链表有环问题随记

    针对面试中遇到的问到的单链表的问题,这里做一个简单的记录。 针对单项链表出现的面试问题,可能涉及一下三个方面 1....

网友评论

    本文标题:单链表有环问题 - 你emo了没

    本文链接:https://www.haomeiwen.com/subject/fxvfwrtx.html