剑指 offer 56:求链表中环的入口节点

剑指offer 56:求链表中环的入口节点

题目:一个链表中包含环,如何找出环的入口节点?

预备知识:判断链表是否有环

用一个指针

该思路比较简单,设置一个指针,依次遍历链表,每走一步判断是否等于头即可。
代码如下:

bool IsCLinkList(LinkList L) {
    if (!L) {
        return false;
    }

    LNode *p = L;
    while (p && p->pNext != L) {
        p = p->pNext;
    }

    return (p == nullptr ? false : true);
}

用两个指针

对于两个指针pSlow和pFast,最初都指向头,pSlow每次只走一步,pFast每次走两步,然后判断两者是否相等。如果是循环链表,那么两者一定会有相遇的时候,否则会退出程序。
代码如下:

bool IsCLinkList_ver2(LinkList L) {
    LNode *pSlow = L;
    if (!pSlow) {
        return nullptr;
    }

    LNode *pFast = pSlow->pNext;

    while (pSlow && pFast) {
        if (pSlow == pFast) {
            return true;
        }
        //pSlow先走一步
        pSlow = pSlow->pNext;

        //pFast后走两步
        pFast = pFast->pNext;
        if (pFast) {
            pFast = pFast->pNext;
        }
    }
    return false;
}

思路分析

回到该题目,思路分析:
a 假设能知道带环部分的节点个数n。那么采用两个指针,先让pFast先走n步,然后再让pSlow从头开始和pFast同时走,他们相遇的点,即为带环链表的入口点。
b 关键是如何得出带环部分的节点个数呢?
首先得找到带环部分的一个节点,然后通过设置两个指针,一个不动,另一个一直往下走,边走边计数,直到两者再次相遇。

主程序编写

根据思路分析,编写代码如下:

//找到带环链表中一个节点
LNode* q56_GetMeetingNode(LinkList L) {
    if (!L) {
        return nullptr;
    }
    LNode *pSlow = L;
    LNode *pFast = pSlow->pNext;

    while (pSlow && pFast) {
        if (pSlow == pFast) {
            return pFast;
        }

        pSlow = pSlow->pNext;
        pFast = pFast->pNext;
        if (pFast) {
            pFast = pFast->pNext;
        }
    }

    return nullptr;
}
//求带环链表入口节点
LNode* q56_GetEntrance(LinkList L) {
    //找到链表带环部分任意一个节点
    LNode *pMeet = q56_GetMeetingNode(L);
    if (!pMeet) {
        return nullptr;
    }

    //求出带环部分共有多少个节点
    int nClistNode = 1;
    LNode *p = pMeet;
    while (p->pNext != pMeet) {
        ++nClistNode;
        p = p->pNext;
    }

    //设置两个指针,让其中一个先走n步,然后同时走,相遇点即为环入口    
    LNode *pFast = L;
    for (int i = 0; i < nClistNode; ++i) {
        pFast = pFast->pNext;
    }

    LNode *pSlow = L;
    while (pSlow != pFast) {
        pSlow = pSlow->pNext;
        pFast = pFast->pNext;
    }
    return pSlow;
}

测试代码

1)建立带环链表
a 先建立循环链表

void CreateCLinkListWithoutHead(LinkList &L, int nInputLength) {
    if (nInputLength < 1) {
        cerr << "argument error";
        return ;
    }

    LNode *pNode = nullptr, *pRear = nullptr;
    cout << "input " << nInputLength << " numbers: ";
    for (int i = 1; i <= nInputLength; ++i) {
        pNode = new LNode;
        cin >> pNode->data;

        if (nullptr == L) {
            L = pNode;
            pRear = pNode;
        } else {
            pRear->pNext = pNode;
            pRear = pRear->pNext;
        }
    }
    pRear->pNext = L;
}

void PrintCLinkListWithoutHead(LinkList L) {
    if (!L) {
        cerr << "   CList is empty.";
        return;
    }
    cout << "   The list is: ";
    LNode *p = L;
    while (p->pNext != L) {
        cout << p->data << " ";
        p = p->pNext;
    }
    cout << p->data;
}

b 后建立单链表,让单链表表尾指向循环链表表头。

void CreateLinkListWithoutHead(LinkList &L, int nInputLength) {
    if (nInputLength < 1) {
        cerr << "argument error";
        return;
    }

    LNode *pNode = nullptr, *pRear = nullptr;
    cout << "input " << nInputLength << " numbers: ";
    for (int i = 1; i <= nInputLength; ++i) {
        pNode = new LNode();
        cin >> pNode->data;

        if (nullptr == L) {
            L = pNode;
            pRear = L;
        } else {
            pRear->pNext = pNode;
            pRear = pRear->pNext;
        }
    }

    pRear->pNext = nullptr;
}

void PrintLinkListWithoutHead(LinkList L) {
    if (nullptr == L) {
        cout << "   List is empty.";
        return ;
    }

    cout << "   The list is: ";

    LNode *p = L;
    while (p) {
        cout << p->data << " ";
        p = p->pNext;
    }
}

2)完整测试代码如下

 //建立单链表
LinkList L1 = nullptr;
int nLength;
cout << "input the length: ";
cin >> nLength;
CreateLinkListWithoutHead(L1, nLength);
PrintLinkListWithoutHead(L1);    cout << endl;

//建立循环链表
LinkList L = nullptr;
cout << "input the length: ";
cin >> nLength;
CreateCLinkListWithoutHead(L, nLength);
PrintCLinkListWithoutHead(L);    cout << endl;

//得到单链表表尾
LNode *p = L1;
while (p->pNext) {
    p = p->pNext;
}
//让单链表表尾指向循环链表表头,得到带环链表
p->pNext = L;

//测试主程序
LNode *pEntrance = q56_GetEntrance(L1);
if (pEntrance) {
    cout << pEntrance->data << endl;
} else {
    cout << "the list doesn't contain circle part.";
}

总结

1)题目本身的难点在于设置两个指针的技巧:
a 让pFast先走n步
b 再让pSlow从头开始和pFast同时走,相遇即是环入口。(我还想了好半天,为什么是这样。最怕的就是这种逻辑问题,不会这样去想,只有多见,多写,多积累,才能在某一天能想出解法。)
2)知道思路之后,写代码,用了大半个小时,其中写循环链表时有些逻辑错误。说明基本功还是不够扎实。

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 、可私信6博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 、可私信6博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值