LeetCode | 19. 删除链表的倒数第 N 个结点
1 相关标签
链表、双指针、C 语言
2 报错情况
2.1 题目
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
2.2 错误代码
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode* removeNthFromEnd(struct ListNode* head, int n){ int distance = 0; // 计数器 struct ListNode *end = head, *del = head, *beforedel = head; // 定位删除的节点 while(end){ if(distance < n){ ++distance; } else{ beforedel = del; // 定位删除节点前的节点 del = del->next; } end = end->next; } // 删除节点 beforedel->next = beforedel->next->next; free(del); return head; }
2.3 测试用例
[1]
1
2.4 报错内容
runtime error: member access within null pointer of type ‘struct ListNode’ [solution.c]
在struct ListNode类型的空指针中访问成员
3 Debug思路
3.1 排查出错原因
运行原有示例中的其他两组
[1,2,3,4,5]
2
[1,2]
1
均输出正确结果。
[1,2,3,5]
[1]
猜测是删除了第1个节点引发的报错,更改示例如下
[1,2,3,4,5]
5
[1,2]
2
运行后出现新的报错
=================================================================
==43==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000030 at pc 0x55fd91f42904 bp 0x7ffe184c15b0 sp 0x7ffe184c15a0
READ of size 4 at 0x602000000030 thread T0
#2 0x7f9486376082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082)
0x602000000030 is located 0 bytes inside of 16-byte region [0x602000000030,0x602000000040)
freed by thread T0 here:
#0 0x7f9486fbe40f in __interceptor_free …/…/…/…/src/libsanitizer/asan/asan_malloc_linux.cc:122
#2 0x7f9486376082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082)
previously allocated by thread T0 here:
#0 0x7f9486fbea06 in __interceptor_calloc …/…/…/…/src/libsanitizer/asan/asan_malloc_linux.cc:153
#4 0x7f9486376082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082)
Shadow bytes around the buggy address:
0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c047fff8000: fa fa fd fd fa fa[fd]fd fa fa 00 00 fa fa 00 00
0x0c047fff8010: fa fa 00 00 fa fa 00 00 fa fa 04 fa fa fa fa fa
0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==43==ABORTING
注释掉 free(del);
后再运行,输出错误结果
[1,3,4,5]
预期结果:[2,3,4,5]
[1]
预期结果:[2]
显然猜测正确
参考力扣官方给出的题解,前言中提到“头节点不存在前驱节点,需要在删除头节点时进行特殊判断”,这类题目通常做法是在头节点前“添加一个哑节点(dummy node)”,再参考力扣的视频题解得知,该题给出的链表不设置空的头节点,链表的头节点中直接存放第一个元素
而错误代码编写时将链表的头节点作为一个空节点计算,代码在删除链表倒数第 n 个节点时删除的是第 2 个节点(如上图存放 2 的节点),而执行 free(del)
后又将第一个节点(head)的内存释放掉,使得返回的头节点为已被释放掉的内存,导致报错
3.2 寻求解决方法
由于头节点不存在前驱节点,仅存在指向头节点的指针,因此需要对删除头节点的情况进行特殊判断
// 删除节点 if(beforedel == del){ head = del -> next; // del -> next 或 beforedel -> next 均可 } else{ beforedel -> next = beforedel -> next -> next; } free(del);
输出正确结果
开销
4 最终结果
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode* removeNthFromEnd(struct ListNode* head, int n){ int distance = 0; // 计数器 struct ListNode *end = head, *del = head, *beforedel = head; // 定位删除的节点 while(end){ if(distance < n){ ++distance; } else{ beforedel = del; // 定位删除节点前的节点 del = del->next; } end = end->next; } // 删除节点 if(beforedel == del){ head = del -> next; // del -> next 或 beforedel -> next 均可 } else{ beforedel -> next = beforedel -> next -> next; } free(del); return head; }
5 总结
删除链表节点问题需要先确定给出链表的头节点是否存放了数据。对于头节点中存在数据的链表,需要另外考虑删除头节点的情况
官方题解采用添加哑节点的方法能让过程更简洁一些,在内存消耗方面也更占优势
本文作者:Guanz
本文链接:https://www.cnblogs.com/Guanz/p/17783576.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步