题目
原文:
Given a circular linked list, implement an algorithm which returns node at the beginning of the loop.
DEFINITION
Circular linked list: A (corrupt) linked list in which a node’s next pointer points to an earlier node, so as to make a loop in the linked list.
EXAMPLE
Input: A –> B –> C –> D –> E –> C [the same C as earlier]
Output: C
译文:
给定一个循环链表,实现一个算法返回这个环的开始结点。
定义:
循环链表:链表中一个结点的指针指向先前已经出现的结点,导致链表中出现环。
例子:
输入:A –> B –> C –> D –> E –> C [结点C在之前已经出现过]
输出:结点C
解答
关于带环链表的题目,《编程之美》中也有讲过。方法很tricky,设置快慢指针, (快指针速度为2,慢指针速度为1)使它们沿着链表移动,如果这个链表中存在环, 那么快指针最终会追上慢指针而指向同一个结点。接下来的问题是,快指针追上慢指针后, 怎么找到这个环的开始结点? 现在我们还没有答案,那让我们先来分析一下,快指针会在哪里追上慢指针。
无图无真相,先上图:
设环的开始结点(图中的D)前有k个结点,环有n个结点(上图中n从D到K共8个结点)。 快指针fast和慢指针slow一开始都指向头结点head,它们移动k步可到环的开始结点。 假设慢指针走过m个结点后,快指针追上了它,这时快指针走过了2m个结点。 快指针比慢指针多走过的结点都在环里转圈了,是环中结点数n的整数倍,即:
2m - m = pn --> m = pn, p为正整数
如果头结点是第一个结点的话,那么相遇结点就是第m+1=pn+1个结点。减去环之前的k 个结点,得到从环开始结点到相遇结点的结点数pn+1-k。相遇结点需要再经过 n-(pn+1-k)+1=(1-p)n+k个结点,才能回到环的开始结点(图中结点D)。 如果让快指针在相遇结点继续走,不过这次把速度变成了慢指针一样, 那么它要走(1-p)n+k步到达环开始结点,让慢指针从头结点head开始走, 它要走k步到达环开始结点。最后,它们将在环开始结点处相遇。
这个是怎么得出来的呢?假设快指针走了(1-p)n+k个结点到达环的开始结点,这时, 慢指针也走了(1-p)n+k步,它离环的开始结点还有
k - [ (1-p)n + k ] = (p-1)n (步)
而这正好是环中结点数的整数倍,所以当慢指针到达环的开始结点时, 快指针(此时它的速度也是1)刚好在环中转了(p-1)圈,然后和慢指针在环的开始结点处相遇。
代码如下:
node* loopstart(node *head){ if(head==NULL) return NULL; node *fast = head, *slow = head; while(fast->next!=NULL){ fast = fast->next->next; slow = slow->next; if(fast==slow) break; } if(fast->next==NULL) return NULL; slow = head; while(fast!=slow){ fast = fast->next; slow = slow->next; } return fast; }
这个思路确实很巧很tricky。但,还有没有别的方法呢?更直观更简单的方法。 既然这么问了,当然是有了。:p一个无环的链表,它每个结点的地址都是不一样的。 但如果有环,指针沿着链表移动,那这个指针最终会指向一个已经出现过的地址。 答案是不是已经呼之欲出了。嗯,没错,哈希表!
以地址为哈希表的键值,每出现一个地址,就将该键值对应的实值置为true。 那么当某个键值对应的实值已经为true时,说明这个地址之前已经出现过了, 直接返回它就OK了。
由于C++标准中没有哈希表的操作,我用map进行模拟。不过哈希表的插入和取值操作是O(1) 的时间。而map是由一个RB tree组织,为了维护这个RB tree,插入和取值都会花更多的时 间。
代码如下:
map<node*, bool> hash; node* loopstart1(node *head){ while(head){ if(hash[head]) return head; else{ hash[head] = true; head = head->next; } } return head; }
完整代码如下:
#include <iostream> #include <map> using namespace std; typedef struct node{ int data; node *next; }node; node* init(int a[], int n, int m){ node *head, *p, *q; for(int i=0; i<n; ++i){ node *nd = new node(); nd->data = a[i]; if(i==m) q = nd; if(i==0){ head = p = nd; continue; } p->next = nd; p = nd; } p->next = q; return head; } node* loopstart(node *head){ if(head==NULL) return NULL; node *fast = head, *slow = head; while(fast->next!=NULL){ fast = fast->next->next; slow = slow->next; if(fast==slow) break; } if(fast->next==NULL) return NULL; slow = head; while(fast!=slow){ fast = fast->next; slow = slow->next; } return fast; } map<node*, bool> hash; node* loopstart1(node *head){ while(head){ if(hash[head]) return head; else{ hash[head] = true; head = head->next; } } return head; } int main(){ int n = 10, m = 9;// m<n int a[] = { 3, 2, 1, 3, 5, 6, 2, 6, 3, 1 }; node *head = init(a, n, m); //node *p = loopstart(head); node *p = loopstart1(head); if(p) cout<<p->data<<endl; return 0; }