一 真题2009-42
2009-42题. (15分)已知一个带有表头节点的单链表,结点结构为:data:link 。假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的data域的值,并返回1;否则,只返回0。要求:
- 描述算法的基本设计思想。
- 描述算法的详细实现步骤。
- 根据设计思想和实现步骤,采用程序设计语言描述算法(使用C、C++或Java语言实现),关键之处请给出简要注释。
二 题目要素解析
- 数据结构:带头结点的单链表(头结点不存有效数据)
- 输入 :头指针
list,正整数k - 输出:
- 成功:打印倒数第
k个结点的data,返回1 - 失败(如
k > 链表长度):返回0
- 成功:打印倒数第
- 约束条件:
- 不能修改链表
- 尽可能高效 → 要求 时间复杂度 O(n),空间复杂度 O(1)
- 核心考点 :双指针法(快慢指针) 求解"倒数第k个结点"
⚠️ 注意:链表带头结点 ,因此第一个有效数据结点是
list->next
三 哔哔详解
这道题的核心是 "高效"------ 最优解法的时间复杂度为 O(n)(仅遍历链表一次),空间复杂度为 O(1)(仅用常数个指针),这也是 408 考研的标准答案思路。
1. 为什么不选 "先遍历求长度,再遍历找位置"?
- 该方法需要遍历链表两次(第一次算长度 n,第二次找第 n−k+1 个节点),时间复杂度虽也是 O(n),但实际效率低于双指针法(少一次遍历),不符合 "尽可能高效" 的要求。
- 双指针法只需遍历一次,是最优解。
2. 双指针法核心逻辑:
- 让快指针 先向前走 k 步,慢指针留在表头节点。
- 然后快慢指针同步向前走,直到快指针走到链表末尾(
link为 NULL)。 - 此时慢指针指向的就是倒数第 *k* 个节点(因为快慢指针始终保持 k 步的距离)。
3. 关键边界条件:
- k 为 0 或负数(题目说明 k 为正整数,需做防御性判断)。
- k 大于链表实际长度(快指针走不到 k 步就到末尾,查找失败)。
- 链表为空(仅有表头节点,无数据节点)。
四 参考答案
1. 算法的基本设计思想
采用双指针(快慢指针)法,核心思路如下:
- 定义两个指针
fast(快指针)和slow(慢指针),初始均指向链表的表头节点。 - 让快指针先向前移动 k 步:若移动过程中快指针提前到达链表末尾,说明 k 超过链表长度,查找失败。
- 快慢指针同步向前移动,直到快指针指向链表最后一个节点(
fast->link == NULL)。 - 此时慢指针指向的节点的下一个节点,就是链表的倒数第 k 个节点(因表头节点无数据,需注意偏移)。
- 验证查找结果,输出对应数据或返回失败标识。
2. 算法的详细实现步骤
步骤 1:合法性校验。
- 若 k≤0(题目要求 k 为正整数),直接返回 0。
- 若链表为空(
list->link == NULL),直接返回 0。
步骤 2:初始化快慢指针。
fast = list,slow = list。
步骤 3:快指针先走 k 步。
-
循环 k 次,每次执行
fast = fast->link:- 若某一步
fast == NULL,说明 k 大于链表长度,返回 0。
- 若某一步
步骤 4:快慢指针同步移动。
- 循环执行
fast = fast->link和slow = slow->link,直到fast == NULL。
步骤 5:查找成功处理。
- 此时
slow指向倒数第 k 个节点,输出slow->data,返回 1。
3. 实现代码
3.1 C 语言
// 定义单链表节点结构(符合题目描述)
typedef struct LNode {
int data; // 数据域(假设数据类型为int,可根据实际调整)
struct LNode *link;// 指针域,指向下一个节点
} LNode, *LinkList;
// 查找链表倒数第k个节点的算法
int FindKthToTail(LinkList list, int k) {
// 步骤1:边界条件校验(k非正/链表为空)
if (k <= 0 || list == NULL || list->link == NULL) {
return 0;
}
// 步骤2:初始化快慢指针,均指向表头节点
LNode *fast = list;
LNode *slow = list;
// 步骤3:快指针先走k步
for (int i = 0; i < k; i++) {
fast = fast->link;
// 若快指针提前到末尾,说明k超过链表长度,查找失败
if (fast == NULL) {
return 0;
}
}
// 步骤4:快慢指针同步移动,直到快指针到末尾
while (fast != NULL) {
fast = fast->link;
slow = slow->link;
}
// 步骤5:查找成功,输出data并返回1
printf("%d\n", slow->data);
return 1;
}
3.2 Java 代码
// 定义单链表节点类(对应题目中的 data:link 结构)
class ListNode {
int data; // 数据域(假设为int类型,可根据需求调整)
ListNode link; // 引用域,指向下一个节点
// 构造方法:创建空节点(用于表头节点)
public ListNode() {
this.data = 0; // 表头节点data无意义,设为默认值
this.link = null;
}
// 构造方法:创建带数据的节点(用于数据节点)
public ListNode(int data) {
this.data = data;
this.link = null;
}
}
// 实现查找倒数第k个节点的算法类
public class FindKthToTail {
/**
* 查找带表头节点单链表的倒数第k个节点
* @param list 链表头指针(指向表头节点)
* @param k 倒数第k个位置(正整数)
* @return 查找成功返回1,失败返回0
*/
public static int findKthToTail(ListNode list, int k) {
// 步骤1:边界条件校验
// 1.1 k非正整数(题目要求k为正整数)
if (k <= 0) {
return 0;
}
// 1.2 链表为空(仅有表头节点,无数据节点)
if (list == null || list.link == null) {
return 0;
}
// 步骤2:初始化快慢引用(替代C语言的指针)
ListNode fast = list; // 快引用
ListNode slow = list; // 慢引用
// 步骤3:快引用先走k步
for (int i = 0; i < k; i++) {
fast = fast.link;
// 若快引用提前到末尾,说明k超过链表长度
if (fast == null) {
return 0;
}
}
// 步骤4:快慢引用同步移动,直到快引用到链表末尾
while (fast != null) {
fast = fast.link;
slow = slow.link;
}
// 步骤5:查找成功,输出data域值并返回1
System.out.println(slow.data);
return 1;
}
// 测试用例(验证算法正确性)
public static void main(String[] args) {
// 1. 构建带表头节点的单链表:表头 -> 1 -> 2 -> 3 -> 4 -> 5
ListNode head = new ListNode(); // 表头节点
ListNode n1 = new ListNode(1);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(3);
ListNode n4 = new ListNode(4);
ListNode n5 = new ListNode(5);
head.link = n1;
n1.link = n2;
n2.link = n3;
n3.link = n4;
n4.link = n5;
// 2. 测试用例1:查找倒数第2个节点(预期输出4,返回1)
System.out.print("查找倒数第2个节点:");
int result1 = findKthToTail(head, 2);
System.out.println("结果:" + (result1 == 1 ? "成功" : "失败"));
// 3. 测试用例2:查找倒数第5个节点(预期输出1,返回1)
System.out.print("查找倒数第5个节点:");
int result2 = findKthToTail(head, 5);
System.out.println("结果:" + (result2 == 1 ? "成功" : "失败"));
// 4. 测试用例3:k超过链表长度(返回0)
System.out.print("查找倒数第6个节点:");
int result3 = findKthToTail(head, 6);
System.out.println("结果:" + (result3 == 1 ? "成功" : "失败"));
// 5. 测试用例4:k为负数(返回0)
System.out.print("查找倒数第-1个节点:");
int result4 = findKthToTail(head, -1);
System.out.println("结果:" + (result4 == 1 ? "成功" : "失败"));
}
}
五 考点精析
5. 1带头结点 vs 不带头结点
- 本题明确"带有表头节点 ",故有效数据从
list->next开始 - 若忽略此点,直接从
list开始计数,会导致结果偏移
5.2 算法复杂度分析
时间复杂度:O(n)。仅遍历链表一次(快指针走 n 步,慢指针走 n−k 步),n 为链表数据节点个数。
空间复杂度 :O(1)。仅使用 fast、slow 两个指针,无额外空间开销。
5.3 双指针法的适用场景
- 求中间结点(快指针走两步,慢指针走一步)
- 判断环(快慢指针追及)
- 求倒数第k个结点(本题)
六 考点跟踪
| 年份 | 题号 | 考查内容 | CSDN 参考链接 | VX参考链接 |
|---|---|---|---|---|
| 2009 | 第 42 题 | 双指针法求倒数第k个结点 |
说明 :本文内容基于公开资料整理,参考了包括但不限于《数据结构》(严蔚敏)、《计算机操作系统》(汤小丹)、《计算机网络》(谢希仁)、《计算机组成原理》(唐朔飞)等国内高校经典教材,以及其他国际权威著作。同时,借鉴了王道、天勤、启航等机构出版的计算机专业考研辅导系列丛书 中的知识体系框架与典型题型分析思路。文中所有观点、例题解析及文字表述均为作者结合自身理解进行的归纳与重述,未直接复制任何出版物原文。内容仅用于学习交流,若有引用不当或疏漏之处,敬请指正。