1、使用双指针找到第K个节点

具体思路:
双指针法:
先假设一个快指针一个慢指针,都指在第一个节点

假设要找倒数第三个节点,那先让快指针走三步,走完之后再让快指针和慢指针同步去走,直到快指针指向了链表末尾的NULL,此时慢指针指到了目标

核心思路:
int findNodeFS(Node *L,int k){
Node *fast=L->next;
Node *slow=L->next;
for (int i = 0; i < k; i++)
{
fast=fast->next;
}
while (fast!=NULL)
{
fast=fast->next;
slow=slow->next;
}
printf("倒数第%d个节点值为:%d\n",k,slow->data);
}
解题:
一,算法基本思想(思路)
在不改变链表结构的前提下,要找到倒数第k个节点,可以用两个指针法:
- 定义两个指针
fast和slow,都指向首元节点(不是头结点)。 - 先让
fast向前移动k步,这样fast与slow之间的距离就为k。 - 然后两个指针同时往后移动,当
fast到达链表末尾时,slow所指的位置就是倒数第 k 个节点。 - 如果
fast提前到达 NULL,说明链表长度小于 k,返回 0。
该算法只需遍历一遍链表,时间复杂度 O(n),空间复杂度 O(1)。
二,算法详细实现步骤
- 用两个指针
fast、slow指向首元节点; fast向前移动k步,如果此时fast == NULL,说明 k 大于链表长度,返回 0;- 否则同时移动
fast和slow,直到fast == NULL; - 此时
slow即为倒数第 k 个节点; - 输出其
data域值并返回 1。
三、C语言完整实现(含注释)
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct node {
ElemType data;
struct node *next;
} Node;
// 查找倒数第k个节点
int findNodeFS(Node *L, int k) {
Node *fast = L->next; // 首元结点
Node *slow = L->next;
int i;
// fast先走k步
for (i = 0; i < k; i++) {
if (fast == NULL) {
return 0; // 链表长度小于k
}
fast = fast->next;
}
// fast和slow同时走,直到fast到达末尾
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
printf("倒数第%d个节点的值为: %d\n", k, slow->data);
return 1;
}
// 辅助函数:创建链表
Node* createList(int n) {
Node *head = (Node*)malloc(sizeof(Node));
head->next = NULL;
Node *tail = head;
for (int i = 1; i <= n; i++) {
Node *p = (Node*)malloc(sizeof(Node));
p->data = i;
p->next = NULL;
tail->next = p;
tail = p;
}
return head;
}
int main() {
Node *L = createList(5); // 创建一个 1→2→3→4→5 的链表
int k = 2;
if (!findNodeFS(L, k))
printf("查找失败:链表长度小于 %d\n", k);
return 0;
}
输出:
倒数第2个节点的值为: 4
四、总结
| 方法 | 时间复杂度 | 空间复杂度 | 思想 |
|---|---|---|---|
| 双指针法 | O(n) | O(1) | 一次遍历即可找到倒数第k个节点 |
2、找相同后缀

核心思路:
- 分别求出两个链表的长度m和n

- fast指针指向较长的链表,其先走m-n或者n-m步

- 同步移动指针,判断他们是否指向同一节点.当指向同一节点时,找到了

核心思路:
typedef struct Node {
char data;
struct Node *next;
} Node;
/* 计算带头结点单链表的长度(不含头结点) */
int length(Node *head) {
int len = 0;
for (Node *p = head->next; p != NULL; p = p->next) len++;
return len;
}
/* 从当前结点向前走 k 步(p 是首元结点而不是头结点) */
Node* advance(Node *p, int k) {
while (k-- > 0 && p) p = p->next;
return p;
}
/* 返回两个带头结点单链表“共享后缀”的起始结点;若无则返回 NULL */
Node* find_common_tail(Node *str1, Node *str2) {
Node *p1 = str1->next; // 首元结点
Node *p2 = str2->next;
int m = length(str1);
int n = length(str2);
// 让较长链表的指针先走 |m-n| 步,对齐剩余长度
if (m > n) p1 = advance(p1, m - n);
else p2 = advance(p2, n - m);
// 同步前进,第一次指针相等处即为共享后缀起点
while (p1 && p2 && p1 != p2) {
p1 = p1->next;
p2 = p2->next;
}
return (p1 == p2) ? p1 : NULL;
}
做题目:
- 基本设计思路
- 设两条带头结点的单链表分别为
str1、str2,首元结点为str1->next、str2->next。 - 先分别求出两表长度
m、n(不含头结点)。 - 让较长表的指针先前进
|m-n|步,使两指针到尾部的剩余长度相同(对齐)。 - 然后两指针同步前进,第一次出现 指针地址相等 的结点即为“共享后缀”的起点;若直到
NULL都未相等,则不存在共享后缀。
关键点:判断“同一结点”必须比较指针地址(p1 == p2),不能比较 data。
- 代码实现(C,含必要注释)
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
char data;
struct Node *next;
} Node;
// 初始化链表(带头结点)
Node* initList() {
Node *head = (Node*)malloc(sizeof(Node));
head->data = 0; // 头结点数据无效
head->next = NULL;
return head;
}
// 创建新节点
Node* newNode(char ch) {
Node *p = (Node*)malloc(sizeof(Node));
p->data = ch;
p->next = NULL;
return p;
}
// 计算链表长度(不含头结点)
int length(Node *L) {
int len = 0;
Node *p = L->next;
while (p) {
len++;
p = p->next;
}
return len;
}
// 从当前结点前进k步
Node* advance(Node *p, int k) {
while (k-- > 0 && p) p = p->next;
return p;
}
// 查找两个链表的公共后缀起点
Node* findCommon(Node *L1, Node *L2) {
Node *p1 = L1->next;
Node *p2 = L2->next;
int len1 = length(L1);
int len2 = length(L2);
// 对齐:让长的先走 |len1 - len2| 步
if (len1 > len2) p1 = advance(p1, len1 - len2);
else p2 = advance(p2, len2 - len1);
// 同步前进,直到相遇
while (p1 && p2 && p1 != p2) {
p1 = p1->next;
p2 = p2->next;
}
return (p1 == p2) ? p1 : NULL;
}
// 打印链表
void printList(Node *L) {
Node *p = L->next;
while (p) {
printf("%c ", p->data);
p = p->next;
}
printf("\n");
}
int main() {
// 创建两个带头结点的链表
Node *str1 = initList();
Node *str2 = initList();
// 创建共享后缀 "i"->"n"->"g"
Node *i = newNode('i');
Node *n = newNode('n');
Node *g = newNode('g');
i->next = n;
n->next = g;
// 构造 str1: l->o->a->d->i->n->g
Node *l = newNode('l');
Node *o = newNode('o');
Node *a = newNode('a');
Node *d = newNode('d');
str1->next = l;
l->next = o;
o->next = a;
a->next = d;
d->next = i;
// 构造 str2: b->e->i->n->g
Node *b = newNode('b');
Node *e = newNode('e');
str2->next = b;
b->next = e;
e->next = i;
// 打印链表
printf("str1: ");
printList(str1);
printf("str2: ");
printList(str2);
// 查找公共后缀起点
Node *p = findCommon(str1, str2);
if (p)
printf("公共后缀起点字符为: %c\n", p->data);
else
printf("无公共后缀\n");
return 0;
}
- 时间复杂度说明
- 计算两个长度:各遍历一次,代价
O(m) + O(n); - 对齐时最多前进
|m-n|步; - 同步扫描最多再走
min(m, n)步; - 整体仍是一次线性遍历数量级,**时间复杂度 **
O(m+n);只用常数个指针变量,**空间复杂度 **O(1)。
3、去除相同元素(绝对值相同)

思路:
题目第一句话的意思是:
节点数是固定的 n 个
每个节点的数据值范围是 [-n, n] 之间
比如说 n = 5,那这个链表中就有 5 个节点,每个节点的 data 满足:
|data| ≤ 5
即 data ∈ {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5}
拿空间换时间,遍历一次即可
做一个数组,下标从0到21,因为链表中的值的绝对值最大是21,我们从链表第一个结点开始,链表第一个值是21,那么数组下标为21处的值设为1,链表第二个数的绝对值是15,那么数组下标为15处的绝对值设成15,链表第三个数的绝对值是15,此时去数组发现下标为15的地方已经标值1了,所以这个是重复的,把链表第三个节点删除.循此往后直到链表末尾即可




核心思路:
void removeNode(Node *L, int n)
{
Node *p = L; // p 指向“待检查结点”的前驱(从头结点开始)
int index;
int *q = (int*)malloc(sizeof(int) * (n + 1)); // 标记数组 seen[0..n]
// 初始化标记数组为 0
for (int i = 0; i < n + 1; i++) *(q + i) = 0;
while (p->next != NULL) {
// 取被检查结点的绝对值
index = abs(p->next->data);
if (*(q + index) == 0) { // 第一次出现:做标记,后移 p
*(q + index) = 1;
p = p->next;
} else { // 重复出现:删除 p->next
Node *temp = p->next;
p->next = temp->next;
free(temp);
}
}
free(q);
}
- 基本设计思路
- 题设给出:链表存 n 个整数,且对任一结点
|data| ≤ n。 - 开一个标记数组
seen[0..n],初值全 0。 - 设指针
p从头结点开始,始终保持p是“待检查结点的前驱”。- 取
index = abs(p->next->data) - 若
seen[index] == 0:说明该绝对值第一次出现,置seen[index]=1,p = p->next - 否则:该绝对值重复,删除
p->next(p->next = p->next->next; free(被删结点)),p不动
- 取
- 一趟扫描完成。
- 结点数据类型定义
typedef struct Node {
int data; // 结点的整数值,可能为负,且 |data| ≤ n
struct Node *next; // 指向后继
} Node;
- C 代码
#include <stdio.h>
#include <stdlib.h> // malloc, free
#include <math.h> // abs
typedef struct Node {
int data;
struct Node *next;
} Node;
/* 带头结点初始化 */
Node* initList(void){
Node *head = (Node*)malloc(sizeof(Node));
head->data = 0;
head->next = NULL;
return head;
}
/* 新结点 */
Node* newNode(int x){
Node *p = (Node*)malloc(sizeof(Node));
p->data = x;
p->next = NULL;
return p;
}
/* 尾插(演示用) */
void push_back(Node *L, int x){
Node *p = L;
while (p->next) p = p->next;
p->next = newNode(x);
}
/* 打印(不含头结点) */
void printList(Node *L){
for (Node *p = L->next; p; p = p->next) printf("%d ", p->data);
printf("\n");
}
/* ---------- 关键:删除绝对值相同(仅保留首次出现) ---------- */
/* 参数 n 是 |data| 的上界(题设给定或可由输入得知) */
void removeNode(Node *L, int n){
Node *p = L; // p 为前驱,从头结点开始
int *seen = (int*)malloc(sizeof(int) * (n + 1));
if (!seen) return;
// 初始化标记数组
for (int i = 0; i <= n; ++i) seen[i] = 0;
while (p->next != NULL){
int index = abs(p->next->data); // 取绝对值
if (seen[index] == 0){ // 首次出现,做标记并前进
seen[index] = 1;
p = p->next;
}else{ // 重复 -> 删除 p->next
Node *temp = p->next;
p->next = temp->next;
free(temp);
}
}
free(seen);
}
int main(){
// head: 21 -> -15 -> -15 -> -7 -> 15
Node *head = initList();
push_back(head, 21);
push_back(head, -15);
push_back(head, -15);
push_back(head, -7);
push_back(head, 15);
printf("原链表: ");
printList(head);
removeNode(head, 21); // n 取绝对值上界(本例为 21)
printf("删除后: ");
printList(head); // 期望输出:21 -15 -7
return 0;
}
- 复杂度
- 时间复杂度:一次线性扫描,每个结点 O(1) 处理 ⇒ O(n)(这里 n 指结点个数)。
- 空间复杂度:标记数组
seen[0..n](这里的 n 为数据上界) ⇒ O(n)。
4、反转链表

思路
- first指向空值,second指向1,third指向second的下一个节点

- 用second指向空值,然后挪,然后再让second指回1,再挪





- 直到second指向NULL,再加个头节点

```c
Node* reverseList(Node *head) {
Node *first = NULL; // 已反转部分的头
Node *second = head->next; // 待反转部分
Node *third; // 暂存下一节点
while (second != NULL) {
third = second->next; // 暂存 next
second->next = first; // 当前节点指向已反转部分
first = second; // first 前进
second = third; // second 前进
}
// 建立新的头结点
Node *hd = initList();
hd->next = first;
return hd;
}
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
// 初始化带头结点的链表
Node* initList(void) {
Node *head = (Node*)malloc(sizeof(Node));
head->data = 0;
head->next = NULL;
return head;
}
// 创建新节点
Node* newNode(int x) {
Node *p = (Node*)malloc(sizeof(Node));
p->data = x;
p->next = NULL;
return p;
}
// 尾插
void push_back(Node *L, int x) {
Node *p = L;
while (p->next) p = p->next;
p->next = newNode(x);
}
// 打印链表
void printList(Node *L) {
Node *p = L->next;
while (p) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
/* ---------------- 链表反转核心函数 ---------------- */
Node* reverseList(Node *head) {
Node *first = NULL; // 已反转部分的头
Node *second = head->next; // 待反转部分
Node *third; // 暂存下一节点
while (second != NULL) {
third = second->next; // 暂存 next
second->next = first; // 当前节点指向已反转部分
first = second; // first 前进
second = third; // second 前进
}
// 建立新的头结点
Node *hd = initList();
hd->next = first;
return hd;
}
/* ---------------- 测试程序 ---------------- */
int main() {
Node *head = initList();
push_back(head, 1);
push_back(head, 2);
push_back(head, 3);
push_back(head, 4);
push_back(head, 5);
printf("原链表: ");
printList(head);
Node *rev = reverseList(head);
printf("反转后: ");
printList(rev);
return 0;
}
5、删除链表中间节点

- fast指向1,slow指向head

- fast走两步(fast=fast->next->next),slow走一步(slow=slow->next)


- 发现fast是NULL或者fast的下一个节点是NULL的时候,

int delMiddleNode(Node *head){
Node *fast=head->next;
Node *slow=head;
while (fast!=NULL && fast->next!=NULL)
{
fast=fast->next->next;
slow=slow->next;
}
// 定义指针变量指向要删除的那个节点
Node *q=slow->next;
slow->next=q->next;
free(q);
return 1;
}
完整代码:
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
// 初始化链表
Node* initList(void) {
Node *head = (Node*)malloc(sizeof(Node));
head->next = NULL;
return head;
}
// 尾插
void push_back(Node *L, int x) {
Node *p = L;
while (p->next) p = p->next;
Node *q = (Node*)malloc(sizeof(Node));
q->data = x;
q->next = NULL;
p->next = q;
}
// 打印
void printList(Node *L) {
Node *p = L->next;
while (p) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
// 删除中间节点
int delMiddleNode(Node *head) {
if (head == NULL || head->next == NULL) return 0; // 空表
Node *fast = head->next;
Node *slow = head;
while (fast != NULL && fast->next != NULL) {
fast = fast->next->next;
slow = slow->next;
}
Node *q = slow->next;
slow->next = q->next;
free(q);
return 1;
}
int main() {
Node *head = initList();
for (int i = 1; i <= 5; i++)
push_back(head, i);
printf("原链表: ");
printList(head);
delMiddleNode(head);
printf("删除中间节点后: ");
printList(head);
return 0;
}
6、做个题目练练

效果如图所示:

- 找到中间的位置,断开


- 把后半截反转

- 6插到1和2中间,5插到2和3中间,4放到3后面.
利用四个指针 p1、p2、q1、q2 实现两条链表的交叉合并。p1 指向第一条链表当前节点, p2 指向 p1 的下一个节点; q1 指向第二条链表当前节点, q2 指向 q1 的下一个节点。每次操作时,先将 q1 插入到 p1 和 p2 之间(即 p1->next=q1 , q1->next=p2),使第二条链表当前节点嵌入第一条链表对应位置;然后四个指针整体后移(p1=p2, q1=q2),继续进行下一轮插入。如此循环,直到任意一条链表遍历完为止,就能让两条链表的节点像拉链一样交错连接成一个完整的新链表

也就是
初始状态
A链表: Head → 1 → 2 → 3 → NULL
B链表: 6 → 5 → 4 → NULL
指针:
p1 → 1
p2 → 2
q1 → 6
q2 → 5
下一步:把6插到1和2中间
操作:
p1->next = q1
q1->next = p2
结果:
Head → 1 → 6 → 2 → 3
B剩余:5 → 4
更新指针:
p1 → 2
p2 → 3
q1 → 5
q2 → 4
下一步:把5插到2和3之间
操作:
p1->next = q1
q1->next = p2
结果:
Head → 1 → 6 → 2 → 5 → 3
B剩余:4
更新指针:
p1 → 3
p2 → NULL
q1 → 4
q2 → NULL
下一步: 把4插到3后面
操作:
p1->next = q1
q1->next = p2 (此时p2为NULL)
结果:
Head → 1 → 6 → 2 → 5 → 3 → 4 → NULL
更新指针:
p1 → NULL
q1 → NULL
循环结束

代码实现:
void reorderList(Node *head) {
if (head == NULL || head->next == NULL) return;
// ① 快慢指针找中点
Node *fast = head->next;
Node *slow = head;
while (fast != NULL && fast->next != NULL) {
fast = fast->next->next;
slow = slow->next;
}
// ② 反转后半链表
Node *first = NULL;
Node *second = slow->next;
slow->next = NULL; // 截断前半部分
Node *third = NULL;
while (second != NULL) {
third = second->next;
second->next = first;
first = second;
second = third;
}
// ③ 交叉合并前后两半
Node *p1 = head->next;
Node *q1 = first;
Node *p2, *q2;
while (p1 != NULL && q1 != NULL) {
p2 = p1->next;
q2 = q1->next;
p1->next = q1;
q1->next = p2;
p1 = p2;
q1 = q2;
}
}
整道题答案:
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
Node* initList(void) {
Node *head = (Node*)malloc(sizeof(Node));
head->next = NULL;
return head;
}
void push_back(Node *L, int x) {
Node *p = L;
while (p->next) p = p->next;
Node *q = (Node*)malloc(sizeof(Node));
q->data = x;
q->next = NULL;
p->next = q;
}
void printList(Node *L) {
Node *p = L->next;
while (p) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
/* -------------------- 重新排列链表 -------------------- */
/*
目标:1→2→3→4→5 → 1→5→2→4→3
步骤:
1. 快慢指针找到中点
2. 反转后半部分链表
3. 合并两部分链表
*/
void reorderList(Node *head) {
if (head == NULL || head->next == NULL) return;
// ① 快慢指针找中点
Node *fast = head->next;
Node *slow = head;
while (fast != NULL && fast->next != NULL) {
fast = fast->next->next;
slow = slow->next;
}
// ② 反转后半链表
Node *first = NULL;
Node *second = slow->next;
slow->next = NULL; // 截断前半部分
Node *third = NULL;
while (second != NULL) {
third = second->next;
second->next = first;
first = second;
second = third;
}
// ③ 交叉合并前后两半
Node *p1 = head->next;
Node *q1 = first;
Node *p2, *q2;
while (p1 != NULL && q1 != NULL) {
p2 = p1->next;
q2 = q1->next;
p1->next = q1;
q1->next = p2;
p1 = p2;
q1 = q2;
}
}
/* -------------------- 测试 -------------------- */
int main() {
Node *head = initList();
for (int i = 1; i <= 5; i++) push_back(head, i);
printf("原链表: ");
printList(head);
reorderList(head);
printf("重新排列后: ");
printList(head);
return 0;
}
7、单链表的局限:
不可以回头
于是想个办法:
- 单向循环链表
循环链表(Circular Linked List)是另一种形式的链式存储结构。其特点是表中最后一个节点的指针域指向头节点,整个链表形成一个环
当链表遍历时,判别当前指针 p 是否指向表尾结点的终止条件不同。在单链表中,判别条件为 p != NULL 或 p->next != NULL,而循环链表的判别条件为 p != L 或 p->next != L

通常这么玩:

fast和slow先都指向head,fast走两步,slow走一步,如果两个指针可以相遇,说明有环
int isCycle(Node *head){
Node *fast=head;
Node *slow=head;
while (fast!=NULL && fast->next!=NULL)
{
fast=fast->next->next;
slow=slow->next;
if (fast==slow)
{
return 1;
}
}
return 0;
}
3784

被折叠的 条评论
为什么被折叠?



