408真题解析-2009-42-数据结构-单链表与双指针技巧

一 真题2009-42

2009-42题. (15分)已知一个带有表头节点的单链表,结点结构为:data:link 。假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的data域的值,并返回1;否则,只返回0。要求:

  1. 描述算法的基本设计思想。
  2. 描述算法的详细实现步骤。
  3. 根据设计思想和实现步骤,采用程序设计语言描述算法(使用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. 算法的基本设计思想

采用双指针(快慢指针)法,核心思路如下:

  1. 定义两个指针 fast(快指针)和 slow(慢指针),初始均指向链表的表头节点。
  2. 让快指针先向前移动 k 步:若移动过程中快指针提前到达链表末尾,说明 k 超过链表长度,查找失败。
  3. 快慢指针同步向前移动,直到快指针指向链表最后一个节点(fast->link == NULL)。
  4. 此时慢指针指向的节点的下一个节点,就是链表的倒数第 k 个节点(因表头节点无数据,需注意偏移)。
  5. 验证查找结果,输出对应数据或返回失败标识。

2. 算法的详细实现步骤

步骤 1:合法性校验。

  • 若 k≤0(题目要求 k 为正整数),直接返回 0。
  • 若链表为空(list->link == NULL),直接返回 0。

步骤 2:初始化快慢指针。

  • fast = listslow = list

步骤 3:快指针先走 k 步。

  • 循环 k 次,每次执行

    复制代码
    fast = fast->link:
    • 若某一步 fast == NULL,说明 k 大于链表长度,返回 0。

步骤 4:快慢指针同步移动。

  • 循环执行 fast = fast->linkslow = 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)。仅使用 fastslow 两个指针,无额外空间开销。

5.3 双指针法的适用场景

  • 求中间结点(快指针走两步,慢指针走一步)
  • 判断环(快慢指针追及)
  • 求倒数第k个结点(本题)

六 考点跟踪

年份 题号 考查内容 CSDN 参考链接 VX参考链接
2009 第 42 题 双指针法求倒数第k个结点

说明 :本文内容基于公开资料整理,参考了包括但不限于《数据结构》(严蔚敏)、《计算机操作系统》(汤小丹)、《计算机网络》(谢希仁)、《计算机组成原理》(唐朔飞)等国内高校经典教材,以及其他国际权威著作。同时,借鉴了王道、天勤、启航等机构出版的计算机专业考研辅导系列丛书 中的知识体系框架与典型题型分析思路。文中所有观点、例题解析及文字表述均为作者结合自身理解进行的归纳与重述,未直接复制任何出版物原文。内容仅用于学习交流,若有引用不当或疏漏之处,敬请指正。

相关推荐
666HZ6662 小时前
数据结构3.0 栈、队列和数组
开发语言·数据结构·算法
tobias.b2 小时前
408真题解析-2009-41-数据结构-最短路径
数据结构·算法·计算机考研·408考研·408真题解析
tobias.b2 小时前
408真题解析-2009-38-网络-TCP累积确认
网络·网络协议·tcp/ip·计算机考研·408真题解析
星火开发设计2 小时前
循环结构进阶:while 与 do-while 循环的适用场景
java·开发语言·数据结构·学习·知识·循环
闪电麦坤953 小时前
Leecode热题100:缺失的第一个正数(数组)
数据结构·算法·leetcode
晚风吹长发13 小时前
初步了解Linux中的动静态库及其制作和使用
linux·运维·服务器·数据结构·c++·后端·算法
SWAGGY..14 小时前
数据结构学习篇(10)--- 二叉树基础oj练习
数据结构·学习
千谦阙听14 小时前
双链表:比单链表更高效的增删查改
数据结构·链表·visual studio
xie_pin_an14 小时前
从二叉搜索树到哈希表:四种常用数据结构的原理与实现
java·数据结构