单链表应用:双指针【快慢指针】

2019 (15 分)已知一个带有表头节点的单链表,节点结构为

data link

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

1)描述算法的基本思想;

2)描述算法的详细实现步骤;

3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用 C、C++、或 Java 语言实现),关键之处请给出简要注释。

1)算法基本思想

采用双指针(快慢指针)法,在不改变链表结构、仅遍历一次链表的前提下,高效找到倒数第 k 个结点:

  • 定义两个指针 fastslow,初始均指向表头节点。
  • 先让 fast 指针向前移动 k 步,使两指针之间保持 k 个结点的距离。
  • 之后 fastslow 同时向后移动,当 fast 到达链表末尾时,slow 恰好指向倒数第 k 个结点。
  • fast 在移动 k 步的过程中提前到达末尾,说明 k 值超过链表长度,查找失败。

2)详细实现步骤

  1. 初始化 :定义指针 fastslow,均指向链表头节点 list
  2. 前向移动 k 步 :循环移动 fast 指针,共 k 次;若循环中 fast 变为 NULL,说明 k 大于链表长度,返回 0。
  3. 同步移动 :当 fast 不为空时,fastslow 同时向后移动,直到 fast 指向链表尾节点的下一个位置(NULL)。
  4. 结果处理 :此时 slow 指向倒数第 k 个结点,输出其 data 域值,返回 1;若步骤 2 中提前结束,返回 0。

3)C 语言实现代码

核心代码

cpp 复制代码
// 链表结点结构定义(需在函数外声明)
typedef struct Node {
    int data;
    struct Node *link;
} Node;

// 核心函数:查找单链表倒数第k个结点
// 参数:list-链表头指针,k-倒数第k个位置
// 返回:查找成功返回1,失败返回0;成功时输出目标结点data值
int findKthFromEnd(Node *list, int k) {
    // 1. 边界条件校验:空链表、k非正均直接失败
    if (list == NULL || k <= 0) return 0;
    
    // 2. 初始化快慢指针
    Node *fast = list, *slow = list;
    
    // 3. 快指针先移动k步,判断k是否超出链表长度
    for (int i = 0; i < k; i++) {
        if (fast == NULL) return 0; // k超过链表长度,返回失败
        fast = fast->link;
    }
    
    // 4. 快慢指针同步移动,直到快指针到链表末尾
    while (fast != NULL) {
        fast = fast->link;
        slow = slow->link;
    }
    
    // 5. 找到目标结点,输出data并返回成功
    printf("%d\n", slow->data);
    return 1;
}

完整代码

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

// 定义链表结点结构
typedef struct Node {
    int data;
    struct Node *link;
} Node;

// 查找倒数第k个结点的算法
int findKthFromEnd(Node *list, int k) {
    if (list == NULL || k <= 0) { // 空链表或k不合法
        return 0;
    }
    Node *fast = list;
    Node *slow = list;

    // fast指针先移动k步
    for (int i = 0; i < k; i++) {
        if (fast == NULL) { // k超过链表长度
            return 0;
        }
        fast = fast->link;
    }

    // 同步移动fast和slow,直到fast到达末尾
    while (fast != NULL) {
        fast = fast->link;
        slow = slow->link;
    }

    // slow指向倒数第k个结点,输出data并返回1
    printf("%d\n", slow->data);
    return 1;
}

// 辅助函数:创建新结点
Node* createNode(int data) {
    Node *newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->link = NULL;
    return newNode;
}

// 辅助函数:尾插法构建链表
void append(Node *head, int data) {
    Node *cur = head;
    while (cur->link != NULL) {
        cur = cur->link;
    }
    cur->link = createNode(data);
}

// 测试主函数
int main() {
    // 创建带头节点的链表
    Node *list = createNode(0); // 表头节点data无实际意义
    append(list, 1);
    append(list, 2);
    append(list, 3);
    append(list, 4);
    append(list, 5);

    int k = 2;
    int res = findKthFromEnd(list, k);
    printf("返回值: %d\n", res); // 预期输出4,返回1

    return 0;
}

算法分析

  • 时间复杂度:O(n),仅遍历链表一次。
  • 空间复杂度:O(1),仅使用两个额外指针,无额外存储空间。
  • 满足题目 "不改变链表、尽可能高效" 的要求。

总结

  1. 核心逻辑:通过快慢指针构建 k 步距离,同步移动后慢指针定位倒数第 k 个结点;
  2. 关键校验:覆盖空链表、k≤0、k 超链表长度三类失败场景;
  3. 效率特性:时间复杂度 O (n)、空间复杂度 O (1),满足题目 "高效且不修改链表" 的要求。
相关推荐
CSharp精选营1 天前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
刘马想放假4 天前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
北域码匠5 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
Darling噜啦啦12 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
小小工匠13 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
玖玥拾13 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
Qres82113 天前
算法复键——树状数组
数据结构·算法
牛油果子哥q13 天前
并查集(DSU)超精讲,路径压缩、按秩合并、万能模板、连通性判定、最小生成树与刷题实战全解
数据结构·c++·最小生成树·并查集
凌波粒13 天前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
WL学习笔记14 天前
单项不带头不循环链表
数据结构·链表