说到烂的面试题-链表是否存在环以及环入口点
2014年01月01日 Algorithm

一个已经快出到烂的面试题,如何检测到链表中存在环,如果有环,找出环入口点。

  1. 一种最简单的情况,假如知道链表的长度,遍历链表,如果得到的长度大于链表长度就存在环;
  2. 将链表逆置的方法,每遍历一个结点就将结点反向逆置,如果存在环,最终还会到达链表的头结点(会破坏链表,检测完成后恢复链表环路);
  3. 元素标记法,每个结点加一个域,isVisited,每访问一个结点标记isVisited,直到检测到某一个被标记过的结点,证明有环(最容易想到的);
  4. (链表在只读区,不允许加标记.)弄一散列表,或者数组也行,遍历每个结点后放入对应散列表或者数组中,每遍历一个结点都检测一下表或者数组中是否已经有了这个结点,如果检测到说明链表含有环;
  5. (内存空间有限,不允许你创建那么大的表或者数组,但是链表长度一定),弄二个指针,一个指向第一个结点不动,另一个指向其后的结点依次后移并与指向第一个结点的指针做比较,然后指向第一个结点的指针再移向第二个结点,另一个指针再向后移动并与之比较,依次进行,检测到二个指针内容相同,刚链表含有环;
  6. (链表长度任意),网上说得最多的方法,快慢指针的方法,二个指针,ptrPre和ptrBack,同时在头结点向后移动,ptrPre每次移动二个结点,ptrBack每次移动一个结点,二者一快一慢,且只相差一个点(类似操场跑步的扣圈),如果有环,那必定二个指针会相遇,检测到二个指针相遇,说明链表中存在环;
  • 一个不知道可不可行的方法:用一个指针去遍历整个链表,把其移动的轨迹(即结点)存放到数组里,然后找这个数组里的最长公共子串,找到后第一个结点就是入口点。
  • 另一种不太常规的方法,也是在找到重合点后,在环的重合点部分断开链表,形成二个相交的链表,找入口问题就转化成了找二个链表的公共交点,不过需要注意的是这种会破坏链表的完整,需要在找到入口点后恢复链表环路。找二个相交链表的公共结点就容易多了,分别拿到二个链表的长度,并算出长度差值,二个指针分别指向二个链表的头,要长的链表先移动长度差值个结点,然后二者同时移动,当检测到二个指针内容相同的时候,即是二个链表相交的结点,也就是原带环链表的入口点。

找到环的入口点:说得最多的方法就是快慢指针找到重合的结点后,重置某一个指针到链表头,二个指针同时移动,每次移动一个结点,当二者相遇的结点处就是环的入口点: 数学解释就是:设链表头到环入口距离是entry_len,,环入口到二个指针重合的位置距离是intersect_len,环的周长是circle_len,相遇时慢指针移动的距离是back_len,那么快指针在相遇的时候移动的距离是2 * back_len,就有:

intersect_len + entry_len = back_len;
2 back_len = N circle_len + back_len;
换算 => N * circle = intersect_len + entry_len;

也就是entry_len = N * circle - intersect_len 且circle是环的周长,即重合位置与入口点剩余的距离与entry_len相同。代码也就是在返回相交点的指针时,用另一个指针同时指针链表头,二个同时移动,直到ptr1 == ptr2,这个结点就是环入口点,实现很简单。下为测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#include <stdio.h>
#include <stdlib.h>
typedef struct Node
{
int data;
struct Node *next;
} Node, *LinkedList;
//==============================链表生成代码_BEGIN_================================
//检测内存分配
void checkMemoryMalloc(LinkedList mem)
{
if (!mem)
{
printf("内存分配失败!\n");
exit(-1);
}
}
//头插法创建一个指定长度的链表
LinkedList createList(int length)
{
LinkedList head = (LinkedList)malloc(sizeof(Node));
checkMemoryMalloc(head);
head->next = NULL;
for (int i = 0; i < length; i++)
{
printf("输入结点数据:\t");
LinkedList pTemp = (LinkedList)malloc(sizeof(Node));
checkMemoryMalloc(pTemp);
scanf("%d", &pTemp->data);
pTemp->next = head->next;
head->next = pTemp;
}
return head;
}
//找到尾结点
LinkedList getTailNode(LinkedList list)
{
LinkedList tail = list->next;
while (tail->next)
tail = tail->next;
return tail;
}
//计算链表长度
int getListLength(LinkedList list)
{
int length = 0;
LinkedList temp = list->next;
while (temp)
{
length++;
temp = temp->next;
}
return length;
}
//根据location,返回对应的结点
LinkedList getNodeByLocation(LinkedList list, int location)
{
int count = 1;
LinkedList temp = list->next;
while (count != location && temp)
{
count++;
temp = temp->next;
}
if (count == location)
return temp;
else
return NULL;
}
//根据结点返回位置
int getLocationByNode(LinkedList list, LinkedList node)
{
LinkedList temp = list->next;
int location = 1;
if (!node)
return -1;
while (temp && temp != node)
{
temp = temp->next;
location++;
}
return location;
}
//做出一个链表的环(部分)
LinkedList makeCycleInList(LinkedList list, int location)
{
LinkedList tailNode = getTailNode(list);
LinkedList tailNext = getNodeByLocation(list, location);
if (!tailNext)
{
printf("位置不正确!\n");
exit(0);
}
tailNode->next = tailNext;
return list;
}
//输出链表元素
void printList(LinkedList list)
{
LinkedList temp = list->next;
while (temp)
{
printf("%d--", temp->data);
temp = temp->next;
}
printf("\n");
}
//构造一个带环链表
LinkedList getSpecialCycleList(int listLength, int cycleLocation)
{
LinkedList cycleList = createList(listLength);
cycleList = makeCycleInList(cycleList, cycleLocation);
return cycleList;
}
//======================================链表生成代码_END_============================
//判定一个链表是否含有环
LinkedList getListIntersectNode(LinkedList list)
{
//二个指针起始偏移相同
LinkedList pBack = list;
LinkedList pPre = list;
while (pPre != NULL && pPre->next != NULL)
{
pBack = pBack->next;//慢的指针,每次移动一个结点
pPre = pPre->next->next;//快的指针,每次移动二个结点
if (pBack == pPre)//二个结点相同的时候,证明有环
return pPre; //将交点返回
}
return NULL;
}
//首次相交的结点切断,成为二个与第一个链表相交的链表
LinkedList cutListOnIntersectNode(LinkedList list)
{
LinkedList cutList = getListIntersectNode(list);
if (!cutList)
{
printf("链表无环!\n");
return NULL;
}
//为切断后的结点新建立一个头结点
LinkedList newHead = (LinkedList)malloc(sizeof(Node));
checkMemoryMalloc(newHead);
newHead->next = cutList->next;//保存交点处下一个结点位置
cutList->next = NULL;//切断交点处指针
return newHead;
}
//得到二个链表公共的结点, 即带环链表的环入口
LinkedList getListsCommonNode(LinkedList first, LinkedList second)
{
int firLength = getListLength(first);
int secLength = getListLength(second);
int diffValue = 0;//保存二个链表长度差值
LinkedList firPtr = first;
LinkedList secPtr = second;
//长的链表指针先移动二个链表长度之差个单位
if (firLength > secLength)
{
diffValue = firLength - secLength;
for (; diffValue--; firPtr = firPtr->next);
}
else
{
diffValue = secLength - firLength;
for (; diffValue--; secPtr = secPtr->next);
}
//查找同一个结点, 二个结点同时移动
while (firPtr && secPtr)
{
firPtr = firPtr->next;
secPtr = secPtr->next;
if (firPtr == secPtr)
return firPtr;
}
return NULL;
}
int main(int argc, const char *argv[])
{
//测试代码
LinkedList firList = getSpecialCycleList(16, 5);
LinkedList secList = cutListOnIntersectNode(firList);
printList(firList);
printf("===============================\n");
printList(secList);
printf("===============================\n");
LinkedList intersectNode = getListsCommonNode(firList, secList);
printf("intersectData == %d\n", intersectNode->data);
printf("firLoc == %d\nsecLoc == %d\n", getLocationByNode(firList, intersectNode), getLocationByNode(secList, intersectNode));
if (getListsCommonNode(firList, secList))
printf("链表交点:%p\n", getListsCommonNode(firList, secList));
else
printf("无交点!\n");
}