题目分析
这是一道经典的面试题,据说还是从微软传出来的,我们来看看这个问题的通用解法——快慢指针,用两个指针pSlow和pFast,就是一个慢指针一个快指针,慢的一次向后跳一步,快的一次向后跳两步,什么时候快的追上慢的了,即当(pSlow == pFast)时就表示有环,若pFast快指针先到了结尾则表示无环,实现的代码如下:
struct listtype
{
int data;
listtype * next;
};
int find_cicle(listtype *head)
{
if (head == NULL)
return -1;
listtype *pFast = head;
listtype *pSlow = head;
while (pFast && pFast->next)
{
pFast = pFast->next->next;
pSlow = pSlow->next;
if (pFast == pSlow)
return 1;
}
return 0;
}
代码简析
- 调用函数返回值为1时,表示单向链表有环;
- 调用函数返回值为0时,表示单向链表无环;
- 调用函数返回值为-1时,表示单向链表无环为空;
- 其中返回0和-1可以合并成一种情况,都是无环。
后续
指针遍历
按道理来说这个问题到这应该是完美了,但是看着这个解法你有没有想过为什么要这么做,我就是这么较真,我就是想知道不这么做到底能不能行,难道这么做效率要高吗?我画了好几次,无论是奇数个节点的环还是偶数个节点的环但没有发现效率提高在哪?难道这么些仅仅是为了好玩?我用一个指针记录一下头指针,然后用一个指针遍历链表,找到了就有环,找不到就无环,难道这样不行吗?这样看着看着我突然想明白了,原来我一直都理解错了,我一直所尝试的环都是左侧图一这种,而真正的链表可能是右侧图二这种,好吧分析到这,指针简单的遍历肯定得pass掉了……
记录访问点
看到图二你或许又想到了方法,把访问过的节点记录一下,然后遍历新节点的时候逐一比较,这个方法可行……,但是咱们是不是还要考虑一下时间复杂度和空间复杂度啊,一共n个节点,当访问第2个节点的时候遍历访问过的1个节点,当访问第3个节点的时候遍历访问过的2个节点……以此类推,当访问第n个节点的时候遍历访问过的n-1个节点,貌似时间复杂度是O( n 2 ),再考虑考虑空间上市不是要存储n个节点信息需要O(n)啊。费时又费力啊,难道你还要把HashTable、HashSet搬出来吗?是时候放弃了,还是选择最初咱们看到的这种方法吧,时间上好像是O(n),空间上完全是O(1)的秒杀啊,不过具体的时间复杂度大家还要仔细考虑一下,肯定是小于等于n次比较的,大家可以算算平均值……,有什么好的想法和建议要告诉我哦,欢迎大家批评指正。