板子:快慢指针(中点、倒数第 k 个、判环)
两个指针速度不同,或者先拉开一段距离再一起走。这样一趟就能找到和表尾位置有关的结点,不用先求表长。
找中点
LNode *middle(LinkList L) { // 返回第 ⌈n/2⌉ 个结点,也就是前半段的最后一个
LNode *slow = L, *fast = L; // 都从头结点出发
while (fast != NULL && fast->next != NULL) { // 保证 fast->next->next 不会访问空指针
slow = slow->next; // 慢指针走一步
fast = fast->next->next; // 快指针走两步
}
return slow; // 空表时返回头结点 L
}关键边界:中点停在哪
| 出发点 | 循环条件 | 1 2 3 4 | 1 2 3 4 5 | 停在 |
|---|---|---|---|---|
头结点 L | fast != NULL && fast->next != NULL | 2 | 3 | 第 |
第一个结点 L->next | 同上 | 3 | 3 | 第 |
要让前半段不短于后半段,就从头结点出发(2019 年重排用的就是这种)。
倒数第 k 个(2009)
int searchK(LinkList list, int k) { // 2009:找到就输出 data 并返回 1,否则返回 0
LNode *p = list->next, *q = list->next;
int cnt = 0; // q 领先 p 的步数
while (q != NULL) {
if (cnt < k) cnt++; // 前 k 步只有 q 走,拉开 k 的距离
else p = p->next; // 之后 p、q 一起走
q = q->next;
}
if (cnt < k) return 0; // 表长不足 k
printf("%d", p->data); // q 走到 NULL 时,p 离 NULL 正好 k 步
return 1;
}判环与环的入口
bool hasCycle(LinkList L) { // 有环时快指针一定会追上慢指针
LNode *slow = L, *fast = L;
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) return true; // 比较的是地址,不是 data
}
return false; // fast 走到了 NULL:无环
}
LNode *cycleEntry(LinkList L) { // 返回环的入口结点;无环返回 NULL
LNode *slow = L, *fast = L;
while (fast != NULL && fast->next != NULL) { // 第一步:和 hasCycle 一样,找相遇点
slow = slow->next;
fast = fast->next->next;
if (slow == fast) break;
}
if (fast == NULL || fast->next == NULL) return NULL; // 是因为走到表尾才退出的:无环
LNode *p = L, *q = slow; // 第二步:一个从起点、一个从相遇点,每次都走一步
while (p != q) { p = p->next; q = q->next; }
return p; // 再次相遇的位置就是入口
}为什么第二步能找到入口:设起点到入口
复杂度:三个功能都是时间
易错点
- 循环条件写
fast != NULL && fast->next != NULL,顺序不能反:&&先判断左边,fast是空指针时就不会再去访问fast->next。 - 倒数第 k 个:
可能比表长还大,要返回 0。 - 判环比较的是指针
slow == fast,不是比较两个结点的data。 - 2009 年原题的指针字段叫
link,考场上按题目的名字写。
链接
- 📕 返回:数据结构代码板子
- ⬅️ 上一篇:删除值为 x 的结点
- ➡️ 下一篇:合并两个有序链表
- 🔗 找中点的用法:链表重排