当前位置: 首页 > news >正文

详解删除链表的倒数第k个结点:双指针法优化与边界处理

详解删除链表的倒数第k个结点:双指针法优化与边界处理

引言

删除链表的倒数第k个结点是链表操作中的经典问题,常见于面试与算法练习中。其核心挑战在于如何高效定位倒数第k个结点并完成删除,同时妥善处理各种边界情况(如k值无效、删除头结点等)。本文将从算法原理出发,优化原始代码的缺陷,提供清晰、健壮的实现方案。

一、问题分析与核心思路

问题定义

给定一个单链表,删除链表的倒数第k个结点,要求尽可能减少遍历次数(最优时间复杂度为O(n),n为链表长度)。

核心思路:双指针法(快慢指针)

传统思路是先遍历链表获取长度n,再遍历到第n-k个结点(倒数第k个结点的前驱),但需两次遍历。
双指针法优化

  1. 让快指针(fast)先走k步;
  2. 慢指针(slow)和快指针同时前进,直到快指针到达链表末尾;
  3. 此时慢指针指向的即为倒数第k个结点。
    若想直接定位前驱结点,可让快指针先走k+1步,此时慢指针最终指向倒数第k+1个结点(即倒数第k个的前驱),简化删除操作。

二、原始代码的缺陷分析

原始代码存在以下问题:

  1. 语法错误:函数返回类型为void,却使用return NULL(C语言中void函数不可返回值);
  2. 指针传递错误:删除头结点时,直接修改函数内的plist(一级指针),无法同步更新外部头指针(值传递特性导致);
  3. 前驱定位复杂:通过count计数获取前驱pPre,逻辑冗余;
  4. 边界处理不完善:如k大于链表长度时,未明确提示或处理;
  5. 命名不规范pListpNode等命名模糊,可读性差。

三、优化后的实现方案

1. 链表结构定义

#include <stdio.h>
#include <stdlib.h>// 链表结点结构
typedef struct ListNode {int val;                  // 结点值struct ListNode* next;    // 指向下一结点的指针
} ListNode;// 创建新结点
ListNode* createNode(int val) {ListNode* node = (ListNode*)malloc(sizeof(ListNode));if (node == NULL) {printf("内存分配失败!\n");exit(1);}node->val = val;node->next = NULL;return node;
}

2. 核心删除函数(双指针法)

/*** 删除链表的倒数第k个结点* @param head 链表头指针的地址(二级指针,用于修改头结点)* @param k 倒数第k个结点(k>0)* @return 成功返回1,失败返回0(如k无效、链表为空等)*/
int deleteLastKNode(ListNode** head, int k) {// 边界检查:头指针为空或k<=0(无效输入)if (head == NULL || *head == NULL || k <= 0) {printf("输入无效:链表为空或k<=0\n");return 0;}// 快指针:先走k+1步,用于定位倒数第k个结点的前驱ListNode* fast = *head;// 慢指针:最终指向倒数第k个结点的前驱ListNode* slow = *head;// 步骤1:快指针先走k步(判断k是否超过链表长度)for (int i = 0; i < k; i++) {if (fast == NULL) {// 快指针提前为空,说明k > 链表长度printf("k值超过链表长度\n");return 0;}fast = fast->next;}// 特殊情况:若快指针此时为空,说明倒数第k个是头结点(k等于链表长度)if (fast == NULL) {ListNode* toDelete = *head;  // 待删除结点为头结点*head = (*head)->next;       // 更新头指针free(toDelete);toDelete = NULL;return 1;}// 步骤2:快指针再走1步(总步数k+1),慢指针开始同步移动fast = fast->next;while (fast != NULL) {fast = fast->next;slow = slow->next;}// 此时slow指向倒数第k+1个结点(前驱),删除其下一个结点(倒数第k个)ListNode* toDelete = slow->next;slow->next = toDelete->next;  // 跳过待删除结点free(toDelete);toDelete = NULL;return 1;
}

四、代码优化点说明

  1. 语法修正:去除void函数的return NULL,改用返回值int标识成功/失败;
  2. 二级指针传递:通过ListNode**head接收头指针地址,确保删除头结点时外部指针能正确更新;
    3.** 简化前驱定位 :快指针先走k+1步,慢指针最终直接指向倒数第k个结点的前驱,无需额外计数;
    4.
    完善边界处理 **:
    • 处理k<=0、链表为空的情况;
    • 检测k是否超过链表长度(快指针提前为空);
    • 单独处理删除头结点的场景(k等于链表长度时);
      5.** 内存安全 **:删除结点后及时free并置空,避免野指针。

五、算法复杂度分析

-** 时间复杂度 :O(n),仅需一次遍历(快指针总步数为n,慢指针同步移动);
-
空间复杂度 **:O(1),仅使用常数个额外指针,无额外空间开销。

六、测试用例与演示

测试步骤

  1. 构建测试链表;
  2. 调用deleteLastKNode删除倒数第k个结点;
  3. 打印链表验证结果。
// 打印链表
void printList(ListNode* head) {ListNode* cur = head;while (cur != NULL) {printf("%d->", cur->val);cur = cur->next;}printf("NULL\n");
}// 测试主函数
int main() {// 构建链表:1->2->3->4->5(长度5)ListNode* head = createNode(1);head->next = createNode(2);head->next->next = createNode(3);head->next->next->next = createNode(4);head->next->next->next->next = createNode(5);printf("原始链表:");printList(head);  // 输出:1->2->3->4->5->NULL// 测试1:删除倒数第2个结点(4)int k = 2;if (deleteLastKNode(&head, k)) {printf("删除倒数第%d个结点后:", k);printList(head);  // 输出:1->2->3->5->NULL}// 测试2:删除倒数第4个结点(2)k = 4;if (deleteLastKNode(&head, k)) {printf("删除倒数第%d个结点后:", k);printList(head);  // 输出:1->3->5->NULL}// 测试3:删除倒数第3个结点(1,头结点)k = 3;if (deleteLastKNode(&head, k)) {printf("删除倒数第%d个结点后:", k);printList(head);  // 输出:3->5->NULL}// 测试4:k=10(超过链表长度)k = 10;deleteLastKNode(&head, k);  // 输出:k值超过链表长度return 0;
}

七、总结

删除链表的倒数第k个结点的关键是双指针法的高效定位,通过一次遍历即可完成前驱结点的查找,时间复杂度优化至O(n)。实现时需重点关注:

  • 二级指针的使用(确保头结点修改生效);
  • 边界情况的全面覆盖(k无效、删除头结点等);
  • 内存安全(及时释放删除的结点)。
http://www.dtcms.com/a/300671.html

相关文章:

  • SpringAI入门及浅实践,实战 Spring‎ AI 调用大模型、提示词工程、对话记忆、Adv‎isor 的使用
  • [spring6: Mvc-异步请求]-源码分析
  • 《 接口日志与异常处理统一设计:AOP与全局异常捕获》
  • 数据结构 堆(4)---TOP-K问题
  • 详解力扣高频SQL50题之1164. 指定日期的产品价格【中等】
  • 【element-ui】HTML引入本地文件出现font找不到/fonts/element-icons.woff
  • Reason-before-Retrieve(CVPR 2025)
  • 网易大模型算法岗面经80道
  • Vim 编辑器工作模式及操作指南
  • ksql连接数据库免输入密码交互
  • Spring Boot + @RefreshScope:动态刷新配置的终极指南
  • C#中Visual Studio平台按照OfficeOpenXml步骤
  • Pinocchio 结合 CasADi 进行 IK 逆运动学及 Mujoco 仿真
  • 【嵌入式硬件实例】-555定时器调光电路实现
  • Java大数据面试实战:Hadoop生态与分布式计算
  • 数据赋能(340)——技术平台——共享平台
  • 不坑盒子:Word里1秒制作“花括号”题目,多音字组词、形近字组词……
  • 零基础学习性能测试第五章:求最佳线程数
  • MySQL 8.0.42创建MGR集群
  • 元宇宙中的“虫洞“:技术实现、应用场景与未来挑战
  • Dify v1.6.0:支持MCP了,为更顺畅的交互打开了大门
  • 【Linux系列】nproc
  • CPA-7-资产减值
  • 墨者:通过手动解决SQL手工注入漏洞测试(MySQL数据库)
  • 握手未来,PostgreSQL认证专家
  • GTP4.0官网版:智能对话与知识引擎,重塑客户服务效率
  • Sql server开挂的OPENJSON
  • USB设备调试
  • 【LeetCode刷题指南】--设计循环队列
  • Java 大视界 -- Java 大数据机器学习模型在电商客户细分与精准营销活动策划中的应用(367)