您的位置: 题酷首页 » 所有题目 » 面试题之链表问题 - 判断单链表是否有环?


面试题之链表问题 - 判断单链表是否有环?


2
2
10 答
10k 看

给定一个单链表:

  1. 如何判断单链表是否有环?
  2. 如何找出环的连接点在哪里?
  3. 如何知道环的长度?
面试题微软链表Google
2009/08/18 by 半瓶墨水 6个月前更新 更新记录



7

使用追赶的方法,很容易得到环的存在性结论。能够解决问题1。记下碰撞点P。
从碰撞点P重新开始追赶操作,再次碰撞。中间经过的操作数就是环长。解决问题3。
分别从碰撞点P和链表头,同步地步进扫描,直到碰撞。此次碰撞点就是链表连接点。解决问题2。


问题2的证明如下:

链表形状类似数字 6 。
假设甩尾(在环外)长度为 a(结点个数),环内长度为 b 。
则总长度(也是总结点数)为 a+b 。
从头开始,0 base 编号。
将第 i 步访问的结点用 S(i) 表示。i = 0, 1 ...
当 i<a 时,S(i)=i ;
当 i≥a 时,S(i)=a+(i-a)%b 。

分析追赶过程。
两个指针分别前进,假定经过 x 步后,碰撞。则有:S(x)=S(2x)
由环的周期性有:2x=tb+x 。得到 x=tb 。
另,碰撞时,必须在环内,不可能在甩尾段,有 x>=a 。

连接点为从起点走 a 步,即 S(a)。
S(a) = S(tb+a) = S(x+a)。
得到结论:从碰撞点 x 前进 a 步即为连接点。

根据假设易知 S(a-1) 在甩尾段,S(a) 在环上,而 S(x+a) 必然在环上。所以可以发生碰撞。
而,同为前进 a 步,同为连接点,所以必然发生碰撞。

综上,从 x 点和从起点同步前进,第一个碰撞点就是连接点。

5 该算法的复杂度:空间复杂度O(1),时间复杂度: 问题1,O(a+(a-b)%b);问题2:O(b);问题3:O(a); 三个问题一起O(n),n=a+b,a、b、n与上面定义相同justforfan528, 2年前
5 @半瓶墨水: 更好的方法?贴出来看看justforfan528, 2年前
2 @半瓶墨水: 对于问题2,你是指“fbird”说的方法吧,时间负责度为O(b),b为环长度;“leaveye”的方法为O(a),a为环外长度;那种方法更快,取决于a>b 还是 a<bjustforfan528, 2年前
虽然有更好的方法,但这个分析很有意思,顶一下半瓶墨水, 2年前
@justforfan528: 可以利用环的长度,双指针就ok了半瓶墨水, 2年前
| 显示所有评论(还有4个)
2

问题2 知道环的长度s,那么p1从链表的头开始,p2开始指向p1的第s个后继结点,p1 p2同步往前移动,当p1==p2时,p1指向的就是环的开始。

1

问题3 after p2 catches p1, let they continue going forward, and count the number of nodes p1 visits (n1). When p2 catches p1 again, n1 is the size of loop.

1

可在”Ge Chunyuan“的方法上加以改进:
1、逆转链表List,若逆转后的头指针与原链表指针相同,则有环,否则无环。若要恢复链表List再逆转一次即可。
2、在逆转链表List的同时,用一双端队列A记录各个节点地址,并计数;在有环的情况下,从队列两端取出节点地址(A[i]、A[j])进行比较,若A[i]==A[j]继续,若A[i]!=A[j]则说明前一节点A[i-1](或A[j+1])即为环的连接点,环的长度即为j-i+2

做个友情连接,^-^
http://qinchong637.javaeye.com

1 1、逆转链表List,若逆转后的头指针与原链表指针相同,则有环,否则无环。若要恢复链表List再逆转一次即可。 我怎么觉得有问题呢?请你给一个例子说明一下吧!shinjikun, 2年前
2 如:1->2->3->4->5->6->3,逆转后:1->2->3->6->5->4->3,转一圈回到原点justforfan528, 2年前
0

判断是否有环。

bool checkLoop(List head)
{
List p1 = head;
List p2 = head;

if (p1 == NULL || p1->next == NULL)
{
   return false;
}   

while(p2 && p2->next)//改成这个样子
{
   if (p2 == p1)
   {
      return true;
   }
   p1 = p1->next;
   p2 = p2->next->next;
}

return false;
}
5 p2=p2->next->next有可能会崩溃半瓶墨水, 2年前
6 不对的吧 你的代码显然在第一次循环的时候就p2=p1了 因为从List p1 = head;List p2 = head;之后p1,p2没有修改过就直接判断if (p2==p1)了Ge Chunyuan, 2年前
0

第二个问题,只要在return true的那个地方 return 那个节点就可以了,那个节点就是连接点了。

5 错,这样追到的地点不一定是哪个节点,你可以试试看半瓶墨水, 2年前
1 哦,我看看。Haitao, 2年前
1 连接点应该是被两个不同的节点同时指向的。BenBear, 2年前
0

我也写个答案

typedef struct LinkedList
{
    int data;
    LinkedList* pNext;  
}LinkedList;

/**
* test 6, write a function to check whether there is loop in a linked list
* @param pList [input] linked list
* @return bool, if there is loop inside, return true, otherwise false
*/

bool isExistLoop(LinkedList* pList) 
{
    if (pList == NULL || pList->pNext == NULL)
        return false;

    LinkedList* p1 = pList;
    LinkedList* p2 = pList;


    while (p2 != NULL && p2->pNext != NULL)
    {
        p1 = p1->pNext;
        p2 = p2->pNext->pNext;

        if (p1==p2)
            return true;
    }
    return false;
}
貌似不错半瓶墨水, 2年前
0

在搞一个破坏性的想法 如果原来的链表是1->2->3->4>5->6->7->8->5这样一个有环的链表 我们可以这样 1.翻转链表 同时记录下整个链表的翻转长度S 并得到翻转以后的链表 1->2->3->4->5 2.遍历链表1->2->3->4->5 得到长度a 3.如果S=a没事, 如果s>a,显然有环,环的入口点在5整个节点。而且环的长度是S-2*a; 这样的效率也是不错的,就是要破坏原来的链表

好办法,只要破坏之后还能再改回来就不算破坏啦半瓶墨水, 2年前
1 1>2>3>4>5>6>4 翻转成啥呢?复杂度得多少?leaveye, 2年前
leaveye, 我的答案是错的,想法有问题.你说的有道理的呵呵Ge Chunyuan, 2年前
此法并非完全不可取,至少可判断是否有环;另外,该方法稍加改造(辅助使用一个双端队列)即可解答这3个问题。justforfan528, 2年前
0

这样如何:

int i, j;
void *backup[MAX];
void *tmp;
void *head = p;

for( i = 0; (p->next != head) && (p->next != NULL);){
tmp = p;
p = backup[i++] = p->next;
tmp->next= head;
}
if ( p == NULL ){
//printf("没上环. - -\n")
}
else{
for ( j = 0; backup[j] != p; j++ );
//1. printf("上环了 -,-\n")
//2. 环在这里---> p
//3. 环的size ---> i-j
}

使用backup,链表可以恢复。
上面只是算法思路,没处理细节,比如极端情况(就是1>2>3>1 这种纯环或空链表)

我看不出来循环是怎么终止的天涯草, 6个月前
0

给定一个单链表:

  1. 如何判断单链表是否有环?
  2. 如何找出环的连接点在哪里?
  3. 如何知道环的长度?

答:
1. 用一个哈希表,用O(n)的时间和空间复杂度解决。从第一个节点开始,把节点放入表。如果这个节点已经在表里,那么说明有环。上面的答案都是至少O(n^2)时间的。
2. 连接点在重复的位置。
3. 把哈希表改成Map<Node,int>。int是当前的位置。如果出现重复k,那么环的大小为当前位置i-k+1。

此法确实可行,但hash表的问题在于hash表的构造,存在效率和通用性的矛盾。对于任意链表,估计这个hash表不太容易兼顾空间复杂度和时间复杂度,且其运行效率也不太稳定,即对于链表A,可能效率很高,而对于链表B其效率可能会降低很多。最坏的情况下,hash表退化成链表,时间复杂度会降低为O(n^2)。justforfan528, 2年前
1 Hash表在最坏的的情况下也不会退化成O(n^2),况且就算目标不能hash,我也可以用排序二叉树来做到O(nlogn)。O(n^2)的情况一定可以避免。shinjikun, 2年前
3 用排序二叉树的确可以做到O(nlogn),但是这样的话除了要进行n次查询外,还要增加了n次插入的代价。另外,如果只是判断有无环,以上方法并非都是O(n^2),“Ge Chunyuan”写的代码(快慢指针法)稍加改进可以实现O(n)内判断是否有环。此外,采用逆转法也可以实现O(n),详见我下面的答案。justforfan528, 2年前

250x

参与回答

 提示:如不是回答问题,请采用发表评论形式! (比如针对题目或者某个回复的意见、建议)

注册登录后再回答