

1.初步思路为两趟扫描,第一趟获取链表节点数cnt,第二趟根据cnt和n找到需要删除的节点,执行删除操作。写出的完整代码如下:
cpp
1. /**
2. * Definition for singly-linked list.
3. * struct ListNode {
4. * int val;
5. * struct ListNode *next;
6. * };
7. */
8. struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
9. // 创建虚拟头节点,统一处理删除头节点的边界情况
10. struct ListNode* dummyHead = (struct ListNode*)malloc(sizeof(struct ListNode));
11. // 虚拟头节点连接原链表
12. dummyHead->next = head;
13. // cur遍历指针,先用来统计链表总节点数量
14. struct ListNode* cur = dummyHead->next;
15. // cnt记录链表有效节点总数
16. int cnt = 0;
17.
18. // 第一次遍历,统计链表节点个数
19. while (cur != NULL){
20. cur = cur->next;
21. cnt++;
22. }
23.
24. // 指针重置回虚拟头,准备找到待删除节点的前驱
25. cur = dummyHead;
26. // 循环移动 cnt-n 次,定位到倒数第n个节点的前一个节点
27. for (int i = 0; i < cnt - n; i++){
28. cur = cur->next;
29. }
30.
31. // 跳过待删除节点,直接连接下下个节点
32. cur->next = cur->next->next;
33. // 返回去掉目标节点后的链表头部
34. return dummyHead->next;
35. }
这其中涉及的知识点:
(1)尽量使用虚拟头结点。
(2)查找的时候只关心链表的真实值,cur指向dummyHead->next。
(3)删除的时候需要找到需要删除节点的前一个节点,cur指向dummyHead。
2.进阶思路限制了只能使用一趟扫描,得知本题可以使用快慢指针,写出的完整代码如下:
cpp
1. /**
2. * Definition for singly-linked list.
3. * struct ListNode {
4. * int val;
5. * struct ListNode *next;
6. * };
7. */
8. struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
9. // 创建虚拟头节点,处理需要删除原头节点的边界场景
10. struct ListNode* dummyHead = (struct ListNode*)malloc(sizeof(struct ListNode));
11. dummyHead->next = head;
12. // cur作为快指针,先向前走n步拉开间距
13. struct ListNode* cur = dummyHead;
14. for (int i = 0; i < n; i++){
15. cur = cur->next;
16. }
17. // 快指针再多走一步,保证慢指针最终停在待删节点前驱
18. cur = cur->next;
19.
20. // del作为慢指针,初始指向虚拟头
21. struct ListNode* del = dummyHead;
22. // 快慢指针同步后移,直到快指针走到链表末尾NULL
23. while (cur != NULL){
24. cur = cur->next;
25. del = del->next;
26. }
27.
28. // 慢指针跳过倒数第n个节点,完成删除
29. del->next = del->next->next;
30.
31. // 返回修改后的链表头节点
32. return dummyHead->next;
33. }
因为n一定小于链表节点数,所以一定不会越界,于是可以让快指针先走n步,然后慢指针再出发,这样可以保证当快指针走到末尾时与慢指针的距离正好为n。同时又因为删除节点需要找到需要删除节点的上一个节点,所以需要让快指针走完n步后再多走一步。