Leetcode-142: Linked List Cycle II

本文介绍了一种经典的链表环检测算法,通过两种不同的方法实现:使用额外数据结构(set)和快慢指针技巧。快慢指针法不仅能够检测到环的存在,还能找到环的入口节点。

这题非常经典,其实没见过的话短时间内想出空间复杂度O(1)的解法我觉得不容易。
解法1:用set或map。但要是要求空间复杂度O(1)就不行了。

ListNode *detectCycle(ListNode *head) {

    if (!head) return NULL;
    set<ListNode *> s;

    ListNode* p = head;
    while(p) {
        if (s.count(p)) {
            return p;
        }
        else {
            s.insert(p);
            p=p->next;
        }
    }

    return NULL;
}

解法2:还是快慢指针法,我是参考网上的解法。
这里写图片描述

如图所示,p1和p2都从head出发,p1每次走1步,p2每次走2步,那么当p1和p2在环中某处相遇时,p2已经走s+m+kr, p1走了s+m+lr,也就是说相遇的时候p2和p1所花次数一样,但p2比p1多走了k-l圈。此处l>=0,k>=1, r为一圈周长。
另外我们知道p2走的路应该是p1的两倍,那么有s+m+kr=2(s+m+lr),可推出s+m=(k-2l)r。设n=k-2l,这个n圈实际上就是s+m的距离。

那么我们有s=nr-m。这个说明什么呢?说明如果让p1从head开始,p2从meet place开始,两个每次都只走一步,则p1走了s步,p2走了nr-m步,两者刚好相遇在环的起始点。

代码如下:

#include <iostream>
#include <set>

using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

ListNode *detectCycle(ListNode *head) {

    if (!head) return NULL;
    ListNode *p1=head, *p2=head;
    while(p2) {
        p1=p1->next;
        p2=p2->next;
        if (!p2 || !p2->next) return NULL;
        p2=p2->next;

        if (p2==p1)
            break;
    }
    p1=head;
    while(p1 && p2 && p1!=p2) {
        p1=p1->next;
        p2=p2->next;
    }

    return p1;
}

int main()
{
    ListNode a=ListNode(1);
    ListNode b=ListNode(2);
    ListNode c=ListNode(3);
    ListNode d=ListNode(4);
    ListNode e=ListNode(5);
    a.next=&b;
    b.next=&c;
    c.next=&d;
    d.next=&e;
    //e.next=&c;

    ListNode* p = detectCycle(&a);
    if (p)
        cout<<p->val<<endl;

    return 0;
}

我的另一个类似解法如下:

/**
 * Definition of singly-linked-list:
 * class ListNode {
 * public:
 *     int val;
 *     ListNode *next;
 *     ListNode(int val) {
 *        this->val = val;
 *        this->next = NULL;
 *     }
 * }
 */

class Solution {
public:
    /**
     * @param headA: the first list
     * @param headB: the second list
     * @return: a ListNode
     */
    ListNode * getIntersectionNode(ListNode * headA, ListNode * headB) {

        if (!headA || !headB) return NULL;
        
        ListNode * nodeA = headA;
        ListNode * endA = NULL;
        
        while(nodeA && nodeA->next) {
            nodeA = nodeA->next;
        }
        endA = nodeA;
        endA->next = headA;  // construct a circle
        
        ListNode * node1 = headB;
        ListNode * node2 = node1->next;
        ListNode * meetNode = NULL;
        
        while(node1 && node2 && node1->next && node2->next) {
            if (node1 == node2) {
                meetNode = node1;
                break;
            }
            node1 = node1->next;
            node2 = node2->next->next;
        }

        if (!meetNode) {
            endA->next = NULL;
            return NULL;
        }
        
        node1 = meetNode->next;
        node2 = headB;
        while(node1 != node2) {
            node1 = node1->next;
            node2 = node2->next;
        }
        
        endA->next = NULL;
        return node1;
    }
};

解法3:参考九章。
思路很清晰。先两个链表各自遍历,如果最后没走到一起就返回NULL。
然后算好两个链表的长度差,短的从头开始走,长的从长度差那个地方开始 走,走到一起的地方就是交汇点。

代码如下:

class Solution {
public:
    /**
     * @param headA: the first list
     * @param headB: the second list
     * @return: a ListNode
     */
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        // write your code here
        if(headA == NULL || headB == NULL)
            return NULL;
        ListNode* iter1 = headA;
        ListNode* iter2 = headB;
        int len1 = 1;
        while(iter1->next != NULL)
        {
            iter1 = iter1->next;
            len1 ++;
        }
        int len2 = 1;
        while(iter2->next != NULL)
        {
            iter2 = iter2->next;
            len2 ++;
        }
        if(iter1 != iter2)
            return NULL;
        if(len1 > len2)
        {
            for(int i = 0; i < len1-len2; i ++)
                headA = headA->next;
        }
        else if(len2 > len1)
        {
            for(int i = 0; i < len2-len1; i ++)
                headB = headB->next;
        }
        while(headA != headB)
        {
            headA = headA->next;
            headB = headB->next;
        }
        return headA;
    }
};
根据原作 https://pan.quark.cn/s/0ed355622f0f 的源码改编 野火IM解决方案 野火IM是专业级即时通讯和实时音视频整体解决方案,由北京野火无限网络科技有限公司维护和支持。 主要特性有:私有部署安全可靠,性能强大,功能齐全,全平台支持,开源率高,部署运维简单,二次开发友好,方便与第三方系统对接或者嵌入现有系统中。 详细情况请参考在线文档。 主要包括一下项目: 野火IM Vue Electron Demo,演示如何将野火IM的能力集成到Vue Electron项目。 前置说明 本项目所使用的是需要付费的,价格请参考费用详情 支持试用,具体请看试用说明 本项目默认只能连接到官方服务,购买或申请试用之后,替换,即可连到自行部署的服务 分支说明 :基于开发,是未来的开发重心 :基于开发,进入维护模式,不再开发新功能,鉴于已经终止支持且不再维护,建议客户升级到版本 环境依赖 mac系统 最新版本的Xcode nodejs v18.19.0 npm v10.2.3 python 2.7.x git npm install -g node-gyp@8.3.0 windows系统 nodejs v18.19.0 python 2.7.x git npm 6.14.15 npm install --global --vs2019 --production windows-build-tools 本步安装windows开发环境的安装内容较多,如果网络情况不好可能需要等较长时间,选择早上网络较好时安装是个好的选择 或参考手动安装 windows-build-tools进行安装 npm install -g node-gyp@8.3.0 linux系统 nodej...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值