行百里者半九十|

Guanz

园龄:3年粉丝:7关注:13

2023-03-06 15:31阅读: 43评论: 0推荐: 0

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 中国大陆许可协议进行许可。

posted @   Guanz  阅读(43)  评论(0编辑  收藏  举报  
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起